
泛型概述以及泛型類
- 泛型就是類型參數(shù)化,處理的數(shù)據(jù)類型不是固定的,而是可以作為參數(shù)傳入;
- 泛型的核心: 告訴編譯器想使用什么類型,然后編譯器幫你處理一切;
public class GenericClass {
private static class Pair<U,V>{
private U first;
private V second;
public Pair(U first, V second) {
this.first = first;
this.second = second;
}
public U getFirst() {
return first;
}
public V getSecond() {
return second;
}
}
public static void main(String[] args){
Pair<String,Integer>pair = new Pair<>("zhangsan", 23);
}
}
為什么Java不直接使用普通的Object類呢 ?
public class GenericClass2 {
private static class Pair{ // Generic Class
private Object first;
private Object second;
public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public Object getSecond() {
return second;
}
}
public static void main(String[] args){
Pair pair = new Pair("zhangsan", 23);
String name = (String) pair.getFirst();
Integer age = (Integer) pair.getSecond();
}
}
其實(shí)是可以這樣的,而且Java的內(nèi)部就是這樣實(shí)現(xiàn)的。
- Java有Java編譯器和Java虛擬機(jī),編譯器將Java源代碼轉(zhuǎn)換為.class文件,虛擬機(jī)加載并運(yùn)行.class文件。
- 對(duì)于泛型類,Java編譯器會(huì)將泛型代碼轉(zhuǎn)換為普通的非泛型代碼,就像上面的普通Pair類代碼及其使用代碼一樣,將類型參數(shù)T擦除,替換為Object,插入必要的強(qiáng)制類型轉(zhuǎn)換。Java虛擬機(jī)實(shí)際執(zhí)行的時(shí)候,它是不知道泛型這回事的,它只知道普通的類及代碼。
- 再次強(qiáng)調(diào),Java泛型是通過擦除實(shí)現(xiàn)的,類定義中的類型參數(shù)如T會(huì)被替換為Object,在程序運(yùn)行過程中,不知道泛型的實(shí)際類型參數(shù),比如Pair<Integer>,運(yùn)行中只知道Pair,而不知道Integer。
那為什么還要使用泛型呢? 泛型有兩個(gè)好處:
- 更好的安全性;
- 更高的可讀性;
泛型方法
- 要定義泛型方法,只需要將泛型參數(shù)列表置于返回值前;
- 注意: 一個(gè)方法是不是泛型的, 和它所在的類是不是泛型沒有任何關(guān)系;
- 泛型方法調(diào)用的時(shí)候,不需要指定類型參數(shù)的實(shí)際類型,Java編譯器會(huì)推斷出來;
public class GenericMethod {
public static <T> int indexOf(T[] arr, T ele){ // Generic Method
for(int i = 0; i < arr.length; i++){
if(arr[i].equals(ele))
return i;
}
return -1;
}
public static void main(String[] args){
System.out.println(indexOf(new Integer[]{1, 3, 5, 7}, 5));
System.out.println(indexOf(new String[]{"zhangsan", "lisi", "wangwu"}, "lisi"));
}
}
實(shí)際上泛型類和泛型方法沒有聯(lián)系:
//泛型類的泛型和泛型方法的泛型沒有一點(diǎn)關(guān)系
public class GenericClassMethod<T> {
public <T> void testMethod(T t){
System.out.println(t.getClass().getName());
}
public <T> T testMethod1(T t){
return t;
}
public static void main(String[] args){
GenericClassMethod<String>gcm = new GenericClassMethod<>();
gcm.testMethod("generic");
Integer res = gcm.testMethod1(new Integer(10));
System.out.println(res);
}
}
// java.lang.String
// 10
三、泛型接口
接口也可以是泛型的,例如,Java中的Comparable和Comparator:
public interface Comparable<T> {
public int compareTo(T o);
}
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}
四、extends、<=、參數(shù)類型必須是給定的或者子類型
1、上界為某個(gè)具體類
- 可以使用extends來限定一個(gè)上界,此時(shí)參數(shù)類型必須是給定的類型或者其子類型;
- 比如定義一個(gè)NumberPair類,限定兩個(gè)參數(shù)類型必須是Number或者子類型,這樣限定之后,在子類中,first、second變量就可以當(dāng)做Number進(jìn)行處理了,比如調(diào)用Number類中的方法doubleValue()、intValue等;
// 示例代碼,(省略了上面的Pair<U,V>類)
public class GenericExtends {
private static class NumberPair<U extends Number, V extends Number> extends Pair<U, V>{
public NumberPair(U first, V second) { // must realize (achieve)
super(first, second);
}
public double getSum(){
return getFirst().doubleValue() + getSecond().intValue();
}
}
public static void main(String[] args){
NumberPair<Double, Integer>np = new NumberPair<>(3.3, 3); // <U, V>可以是 Number的子類,即 <=
System.out.println(np.getSum());
}
}
2.上界為某個(gè)接口
在泛型方法中,一種常見的限定類型是必須實(shí)現(xiàn)Comparable接口:
- 下面的例子,要進(jìn)行元素的比較,要求元素必須實(shí)現(xiàn)Comparable接口, 所以給類型參數(shù)設(shè)置了一個(gè)上邊界Comparable 必須實(shí)現(xiàn)Comparable接口;
- 可以理解為: T是一種數(shù)據(jù)類型,必須實(shí)現(xiàn)Comparable,且必須可以與相同類型的元素進(jìn)行比較;
ublic class GenericExtends2 {
// 要進(jìn)行元素的比較,要求元素必須實(shí)現(xiàn)Comparable接口
// 所以給類型參數(shù)設(shè)置了一個(gè)上邊界Comparable,T 必須實(shí)現(xiàn)Comparable接口
public static <T extends Comparable> T getMax(T[] arr){
T max = arr[0];
for(int i = 0; i < arr.length; i++){
if(arr[i].compareTo(max) > 0){
max = arr[i];
}
}
return max;
}
// 不過上面這么寫會(huì)有警告 因?yàn)镃omparable是一個(gè)泛型接口,它也需要一個(gè)類型參數(shù),所以下面的寫法比較好
// 理解: T是一種數(shù)據(jù)類型,必須實(shí)現(xiàn)Comparable,且必須可以與相同類型的元素進(jìn)行比較
public static <T extends Comparable<T> > T getMax2(T[] arr){
T max = arr[0];
for(int i = 0; i < arr.length; i++){
if(arr[i].compareTo(max) > 0){
max = arr[i];
}
}
return max;
}
}
3.上界為其他參數(shù)類型
- 這里模仿ArrayList來創(chuàng)建一個(gè)類, 并想著實(shí)現(xiàn)其中的addAll()方法,但是如果不使用一個(gè)上界的話,會(huì)出現(xiàn)無法添加子類的情況,看下面的代碼,Number的集合理應(yīng)可以添加Integer類型的元素。
public class GenericExtends3 {
// seems like ArrayList
private static class DynamicArray<E>{
private static final int DEFAULT_CAPACITY = 10;
private int size;
private Object[] data;
public DynamicArray() {
this.data = new Object[DEFAULT_CAPACITY];
}
private void ensureCapacity(int minCapacity){ // simulate ArrayList
int oldCapacity = data.length;
if(oldCapacity >= minCapacity)
return;
int newCapacity = oldCapacity * 2;
if(newCapacity < minCapacity) //如果擴(kuò)展2倍還是小于minCapacity,就直接擴(kuò)展成為minCapacity
newCapacity = minCapacity;
data = Arrays.copyOf(data, newCapacity);
}
public void add(E e){
ensureCapacity(size + 1);
data[size++] = e;
}
public E get(int index){
return (E)data[index];
}
public int size(){
return size;
}
public E set(int index, E e){
E oldValue = get(index);
data[index] = e;
return oldValue;
}
public void addAll(DynamicArray<E>arr){
for(int i = 0; i < arr.size; i++){
add(arr.get(i));
}
}
}
public static void main(String[] args){
DynamicArray<Number>numbers = new DynamicArray<>();
DynamicArray<Integer>ints = new DynamicArray<>();
ints.add(10);
ints.add(20);
// numbers.addAll(ints); // compile error
}
}
那個(gè)需求感覺上是可以,但是通過反證法可以發(fā)現(xiàn)是行不通的,看下面代碼以及解釋:
DynamicArray<Number>numbers = new DynamicArray<>();
numbers = ints; // 假設(shè)合法
numbers.add(new Double(3.3)); // 那么這一樣也可以,此時(shí)因?yàn)閚umbers和ints指向的同一個(gè)堆區(qū)空間,則ints中出現(xiàn)double類型值,顯然不合理
//再看一個(gè)例子
List<Object>olist = null;
List<String>slist = new ArrayList<>();
olist = slist; // err
//如果上述假設(shè)合理
olist.add(111);
//則slist中就會(huì)出現(xiàn)Integer類型的值,顯然不合理
所以,可以使用上界類型將addAll方法改進(jìn)如下:
//傳入的是T類型,限定為是E類型或者E的子類類型
public <T extends E>void addAll(DynamicArray<T>arr){
for(int i = 0; i < arr.size; i++){
add(arr.get(i));
}
}
五、通配符?
1、有限定類型通配符的簡(jiǎn)單使用
public void addAll(DynamicArray<? extends E>arr){
for(int i = 0; i < arr.size; i++){
add(arr.get(i));
}
}
<? extends E>表示有限定通配符,匹配E或E的某個(gè)子類型,具體是什么子類型是未知的。 看一下public <T extends E>void addAll(DynamicArray<T>arr) 和public void addAll(DynamicArray<? extends E>arr)的區(qū)別:
-
<T extends E>用于定義類型參數(shù),它聲明了一個(gè)類型參數(shù)T,可放在泛型類中類名的后面、泛型方法返回值前面; -
<? extends E>用于實(shí)例化類型參數(shù),它用于實(shí)例化泛型變量中的類型參數(shù),只是這個(gè)具體類型是未知的,只知道它是E或E的子類型;
2、無限定類型通配符
簡(jiǎn)單使用: 第一種方式使用通配符,第二種方式使用類型參數(shù),可以達(dá)到同樣的目的:
//使用通配符
public static int indexOf(DynamicArray<?> arr, Object elm){
for(int i = 0; i < arr.size(); i++){
if(arr.get(i).equals(elm))
return i;
}
return -1;
}
//使用類型參數(shù) type parameter
public static <T> int indexOf2(DynamicArray<T> arr, Object elm){
for(int i = 0; i < arr.size(); i++){
if(arr.get(i).equals(elm))
return i;
}
return -1;
}
但是通配符也有一些限制
- 1)、第一條限制: 只能讀,不能寫
比如 ,下面三行代碼就會(huì)報(bào)錯(cuò) :
public class WildcardCharacter {
public static void main(String[] args){
ArrayList<Integer> ints = new ArrayList<>();
ArrayList<? extends Number> numbers = ints; // 使用extends通配符指定上界
Integer a = 10;
// numbers.add(a); // err
// numbers.add(Object(a)); //err
// numbers.add(Number(a)); //err
}
}
- 解釋: ?表示類型安全無知,? extends Number表示是Number的某個(gè)子類型,但不知道具體子類型,如果允許寫入,Java就無法確保類型安全性,所以干脆禁止;
- 這種限制關(guān)系是好的,但是這使得很多理應(yīng)可以完成的操作可能會(huì)出現(xiàn)錯(cuò)誤;
比如: 下面的代碼中最后兩行會(huì)報(bào)錯(cuò),原因就是不能修改?通配符的值:
public static void swap(ArrayList<?> arr, int i, int j){
Object tmp = arr.get(i);
arr.set(i, arr.get(j)); // can't change the value
arr.set(j, tmp);
}
再看一個(gè)例子:在方法傳遞參數(shù)的時(shí)候,不能往參數(shù)中添加元素:
public class GenericExtends4 {
private static class Fruit {}
private static class Apple extends Fruit{}
private static class Pear extends Fruit{}
private static class FuShiApple extends Apple{}
static class Clazz<T extends Fruit>{ //創(chuàng)建的類必須是Fruit的子類//為了自己類中使用這個(gè)類
}
public static void main(String[] args) {
Clazz<Fruit>t = new Clazz<>(); // <= 關(guān)系
Clazz<Apple>t2 = new Clazz<>();
Clazz<Pear>t3 = new Clazz<>();
Clazz<FuShiApple>t4= new Clazz<>();
//調(diào)用方法
List<? extends Fruit> list1 = new ArrayList<>();
add(list1);
List<Fruit> list2 = new ArrayList<>();
add(list2);
List<Apple> list3 = new ArrayList<>();
add(list3);
List<? extends Apple> list4 = new ArrayList<FuShiApple>(); //存放Apple以及它的子類
add(list4);
List<FuShiApple> list5 = new ArrayList<>();
add(list5);
//?為什么錯(cuò)誤 : 因?yàn)?? 等同于? extends Object :不是<= Fruit的 下面兩個(gè)是一樣的
List<?>list6 = new ArrayList<>();
List<? extends Object>list7 = new ArrayList<>();
//add(list6); // err
//add(list7); // err
}
// 為了保證向下兼容的一致性,不能添加元素
public static void add(List<? extends Fruit> list) {
/** 不能往里面加這樣的對(duì)象 不能用于添加數(shù)據(jù)
list.add(new Fruit());
list.add(new Apple());
list.add(new Pear());
*/
list.add(null);
}
}
- 2)、第二條限制: 參數(shù)類型間的依賴關(guān)系
如果參數(shù)類型之間有依賴關(guān)系,也只能用類型參數(shù),比如下面的例子:
// S和D要么相同,要么S是D的子類,否則類型不兼容,有編譯錯(cuò)誤
public static <D,S extends D> void copy(ArrayList<D> dest, ArrayList<S> src){
for(int i=0; i<src.size(); i++)
dest.add(src.get(i));
}
// 可以使用通配符簡(jiǎn)化一下
public static <D> void copy2(ArrayList<D> dest, ArrayList<? extends D> src){
for(int i=0; i<src.size(); i++)
dest.add(src.get(i));
}
- 3)、第三條限制: 如果返回值依賴于類型參數(shù),也不能用通配符
//不能使用通配符,只能用類型參數(shù),因?yàn)橐祷?public static <T extends Comparable<T> > T max(ArrayList<T> arr){
T max = arr.get(0);
for(int i = 1; i < arr.size(); i++){
if(arr.get(i).compareTo(max)>0){
max = arr.get(i);
}
}
return max;
}
那么到底該用通配符還是類型參數(shù)呢?
- 通配符形式都可以用類型參數(shù)的形式來替代,通配符能做的,用類型參數(shù)都能做。
- 通配符形式可以減少類型參數(shù),形式上往往更為簡(jiǎn)單,可讀性也更好,所以,能用通配符的就用通配符。
- 如果類型參數(shù)之間有依賴關(guān)系,或者返回值依賴類型參數(shù),或者需要寫操作,則只能用類型參數(shù)。
通配符形式和類型參數(shù)往往配合使用,比如,上面的copy2方法,定義必要的類型參數(shù),使用通配符表達(dá)依賴,并接受更廣泛的數(shù)據(jù)類型。
六、super、>=、超類型通配符
- 簡(jiǎn)單的來說,super和extends剛好相反,匹配的是>= E的類型;
- 相當(dāng)于是規(guī)定了一個(gè)下界,可以匹配 >=的類型;
1、使用場(chǎng)景
看它的使用場(chǎng)景, 在DynamicArray中添加一個(gè)copyTo方法,功能是將當(dāng)前對(duì)象容器中的數(shù)拷貝到傳入的參數(shù)dest容器中:
//add current value to the dest collection
public void copyTo(DynamicArray<E>dest){
for(int i = 0; i < dest.size(); i++)
dest.add(this.get(i));
}
然后不使用super,看下面的代碼,最后一行就會(huì)報(bào)錯(cuò),但是將Integer數(shù)組拷貝到Number數(shù)組理應(yīng)是可以的:
public static void main(String[] args){
DynamicArray<Integer>ints = new DynamicArray<>();
ints.add(3);
ints.add(4);
DynamicArray<Number>nums = new DynamicArray<>();
ints.copyTo(nums); // 將ints 中的元素拷貝到nums,本應(yīng)該是可以的,但是如果沒有? super E就不行
}
使用超類型通配符就可以解決上面的問題:
public void copyTo(DynamicArray<? super E>dest){
for(int i = 0; i < dest.size(); i++)
dest.add(this.get(i));
}
2、沒有< T super E>(有<T extend E>)
比較類型參數(shù)限定與超類型通配符,類型參數(shù)限定只有extends形式,沒有super形式,比如前面的copyTo方法,它的通配符形式的聲明為:
public void copyTo(DynamicArray<? super E> dest)
如果類型參數(shù)限定支持super形式,則應(yīng)該是:
public <T super E> void copyTo(DynamicArray<T> dest)
但是,Java并不支持這種語法。對(duì)于有限定的通配符形式<? extends E>,可以用類型參數(shù)限定替代,但是對(duì)于類似上面的超類型通配符,則無法用類型參數(shù)替代。
再看和extends使用方法傳遞參數(shù)的對(duì)比: (在方法傳遞中可以添加自己和子類的數(shù)據(jù), 區(qū)別于extends,extends都不可以添加)
public class GenericSuper2 {
private static class Fruit {}
private static class Apple extends Fruit{}
private static class Pear extends Fruit{}
private static class FuShiApple extends Apple{}
static class Clazz<T extends Fruit>{ //創(chuàng)建的類必須是Fruit的子類//為了自己類中使用這個(gè)類
}
public static void main(String[] args) {
List<Apple>list1 = new ArrayList<>();
add(list1);
List<Fruit>list2 = new ArrayList<>();
add(list2);
List<Object>list3 = new ArrayList<>();
add(list3);
//?super的使用
List<? super Apple>list4 = new ArrayList<>();
add(list4);
List<? super Apple>list5 = new ArrayList<>();
add(list5);
List<FuShiApple>list6 = new ArrayList<>(); // < 的不行
// add(list6); // err
}
//只要是Apple的祖先都可以調(diào)用這個(gè)方法 >=
public static void add(List <? super Apple> list) {
/*** 不能用于添加父類對(duì)象的數(shù)據(jù)
* list.add(new Fruit());
*/
//區(qū)別于extends, 可以添加自己和子類的數(shù)據(jù)
list.add(new Apple());
list.add(new FuShiApple());
}
}
七、通配符extends、super比較
通配符比較:
共同點(diǎn): 目的都是為了使方法接口更為靈活,可以接受更為廣泛的類型。
<? super E>用于靈活寫入或比較,使得對(duì)象可以寫入父類型的容器(>=),使得父類型的比較方法可以應(yīng)用于子類對(duì)象。
<? extends E>用于靈活讀取,使得方法可以讀取E或E的任意子類型的容器對(duì)象。
Java容器類的實(shí)現(xiàn)中,有很多這種用法,比如,Collections中就有如下一些方法:
public static <T extends Comparable<? super T>> void sort(List<T> list)
public static <T> void sort(List<T> list, Comparator<? super T> c)
public static <T> void copy(List<? super T> dest, List<? extends T> src)
public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp)
八、泛型擦除
泛型信息只存在于代碼編譯階段,在進(jìn)入 JVM 之前,與泛型相關(guān)的信息會(huì)被擦除掉,專業(yè)術(shù)語叫做類型擦除。
通俗地講,泛型類和普通類在 java 虛擬機(jī)內(nèi)是沒有什么特別的地方;
看下面代碼:
public class GenericWipe {
public static void main(String[] args){
List<String> slist = new ArrayList<>();
List<Integer> ilist = new ArrayList<>();
System.out.println(slist.getClass() == ilist.getClass());
}
}
這段代碼的輸出結(jié)果是true。正如一開始說的,編譯器會(huì)將T擦除,然后替換成為Object(并不完全正確),在必要的時(shí)候進(jìn)行強(qiáng)制類型轉(zhuǎn)換。
再看以下代碼的輸出結(jié)果:
public class GenericWipe<T> {
private T obj;
public GenericWipe(T obj){
this.obj = obj;
}
public static void main(String[] args){
GenericWipe<String>gw = new GenericWipe<>("wipe");
Class gwClass = gw.getClass();
System.out.println(gwClass.getName()); // 得到運(yùn)行時(shí)的狀態(tài)信息,運(yùn)行時(shí)是真實(shí)的類型
System.out.println("--------------------------");
Field[] fs = gwClass.getDeclaredFields(); //得到在JVM中的類型
for ( Field f:fs)
System.out.println("Field name " + f.getName() + " type:" + f.getType().getName());
}
}
JavaPrimary.Generic.GenericWipe
--------------------------
Field name obj type:java.lang.Object
第一種類型是Class 的類型是 GenericWipe,并不是 GenericWipe<T> 這種形式,第二種類型是Jvm中的類型; 那是不是泛型類被類型擦除后,相應(yīng)的類型就被替換成 Object 類型呢?這種說法不是完全正確的。
更改一下代碼:
public class GenericWipe<T extends String> { // <= String
private T obj;
public GenericWipe(T obj){
this.obj = obj;
}
public static void main(String[] args){
GenericWipe<String>gw = new GenericWipe<>("wipe");
Class gwClass = gw.getClass();
System.out.println(gwClass.getName()); // 得到運(yùn)行時(shí)的狀態(tài)信息,運(yùn)行時(shí)是真實(shí)的類型
System.out.println("--------------------------");
Field[] fs = gwClass.getDeclaredFields(); //得到在JVM中的類型
for ( Field f:fs)
System.out.println("Field name " + f.getName() + " type:" + f.getType().getName());
}
}
輸出結(jié)果:
JavaPrimary.Generic.GenericWipe
--------------------------
Field name obj type:java.lang.String
可以看到,第二個(gè)輸出變成了String。所以結(jié)論如下:
- 在泛型類被類型擦除的時(shí)候,之前泛型類中的類型參數(shù)部分如果沒有指定上限,如<T>則會(huì)被轉(zhuǎn)譯成普通的 Object 類型;
- 如果指定了上限如 <T extends String> 則類型參數(shù)就被替換成類型上限。
所以,在反射中,add() 這個(gè)方法對(duì)應(yīng)的 Method 的簽名應(yīng)該是 Object.class。也就是說,如果你要在反射中找到 add 對(duì)應(yīng)的 Method,你應(yīng)該調(diào)用 getDeclaredMethod("add",Object.class) 否則程序會(huì)報(bào)錯(cuò),提示沒有這么一個(gè)方法,原因就是類型擦除的時(shí)候,T 被替換成 Object 類型了。
public class GenericWipe<T> { // <= String
// public class GenericWipe<T extends String> { // <= String
private T obj;
public GenericWipe(T obj){
this.obj = obj;
}
public void add(T obj){
}
public static void main(String[] args){
GenericWipe<String>gw = new GenericWipe<>("wipe");
Class gwClass = gw.getClass();
System.out.println(gwClass.getName()); // 得到運(yùn)行時(shí)的狀態(tài)信息,運(yùn)行時(shí)是真實(shí)的類型
System.out.println("--------------------------");
Method[] methods = gwClass.getDeclaredMethods();
for ( Method m:methods ){
System.out.println(" method:" + m.toString());
}
}
}
九、泛型注意事項(xiàng)
1、基本類型不能用于實(shí)例化類型參數(shù),也就是泛型類或者泛型方法中,不接受 8 種基本數(shù)據(jù)類型。
比如:
List<int> li = new ArrayList<>(); // err
List<boolean> li = new ArrayList<>(); // err
List<Integer> li = new ArrayList<>(); //ok
List<Boolean> li1 = new ArrayList<>(); // ok
2、運(yùn)行時(shí)類型信息不適用于泛型
這個(gè)也就是上面說的泛型擦除,泛型不支持運(yùn)行時(shí)的信息(和反射有關(guān))。
instanceof后面是接口或類名,instanceof是運(yùn)行時(shí)判斷,也與泛型無關(guān),所以,Java也不支持類似如下寫法:
if(p1 instanceof Pair<Integer>)
3、Java 不能創(chuàng)建具體類型的泛型數(shù)組
例如下面的list1和list2創(chuàng)建是錯(cuò)誤的,但是后面的?可以,因?yàn)?代表的是未知類型:
public class GenericOther {
public static void main(String[] args){
// List<Integer>[] list1 = new ArrayList<Integer>[]; // complier err
// List<Boolean> list2 = new ArrayList<Boolean>[]; // complier err
List<?>[] list3 = new ArrayList<?>[10]; // 這個(gè)卻可以 ? 代表的是未知類型
list3[1] = new ArrayList<String>();
List<?> tmp = list3[1];
System.out.println(tmp.get(0));
// tmp.set(1, 2); complier err
}
}
-
List<Boolean>和List<Boolean>在 Jvm 中等同于List<Object>,所有的類型信息都被擦除,程序也無法分辨一個(gè)數(shù)組中的元素類型具體是List<Integer>類型還是List<Boolean>類型。 -
?代表未知類型,涉及的操作都基本上與類型無關(guān),Jvm 不針對(duì)它對(duì)類型作判斷,因此它能編譯通過,但是,它只能讀,不能寫。比如,上面的 tmp 這個(gè)局部變量,它只能進(jìn)行 get() 操作,不能進(jìn)行 add() 操作。
再從如果可以創(chuàng)建泛型數(shù)組會(huì)出現(xiàn)什么樣的問題來看: 數(shù)組可以進(jìn)行不同類型之間的轉(zhuǎn)換,但是也需要注意使用,使用不當(dāng)就會(huì)造成運(yùn)行時(shí)異常,而如果運(yùn)行創(chuàng)建泛型數(shù)組也會(huì)產(chǎn)生類似的問題,所以Java干脆禁止。
public class NoGenericClassArray {
private static class Pair { // Generic Class
private Object first;
private Object second;
public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public Object getSecond() {
return second;
}
}
public static void main(String[] args) {
// 數(shù)組是Java直接支持的概念,它知道數(shù)組元素的實(shí)際類型,
// 它知道Object和Number都是Integer的父類型,所以這個(gè)操作是允許的。
Integer[] ints = new Integer[10];
Number[] numbers = ints; // is ok
Object[] objs = ints;
// 雖然Java允許這種轉(zhuǎn)換,但是如果使用不恰當(dāng),就有可能引起運(yùn)行時(shí)異常
Integer[] ints2 = new Integer[10];
Object[] objs2 = ints2;
objs2[0] = "hello"; // RuntimeException
// Pair<Object, Integer>[] options = new Pair<Object, Integer>[3]; //如果可以,那最后一行就會(huì)不會(huì)編譯錯(cuò)誤,這樣顯然是不行的
// Object[] objs = options;
// objs[0] = new Pair<Double, String>(12.34, "hello");
}
}
4、不能通過類型參數(shù)創(chuàng)建對(duì)象
下面的寫法是非法的。
T elm = new T();
T[] arr = new T[10];
如果允許,本來你以為創(chuàng)建的就是對(duì)應(yīng)類型的對(duì)象,但由于類型擦除,Java只能創(chuàng)建Object類型的對(duì)象,而無法創(chuàng)建T類型的對(duì)象。 那如果確實(shí)希望根據(jù)類型創(chuàng)建對(duì)象呢?需要設(shè)計(jì)API接受類型對(duì)象,即Class對(duì)象,并使用Java中的反射機(jī)制,如果類型有默認(rèn)構(gòu)造方法,可以調(diào)用Class的newInstance方法構(gòu)建對(duì)象:
public static <T> T create(Class<T> type){
try {
return type.newInstance();
} catch (Exception e) {
return null;
}
}
5、泛型類類型參數(shù)不能用于靜態(tài)變量和方法,泛型類中泛型只能用在成員變量上,只能使用引用類型,在接口中泛型只能只能用在抽象方法中,全局常量不能使用泛型
對(duì)于泛型類聲明的類型參數(shù),可以在實(shí)例變量和方法中使用,但在靜態(tài)變量和靜態(tài)方法中是不能使用的。下面的寫法是非法的:
public class Singleton<T> {
private static T instance;
public synchronized static T getInstance(){
if(instance==null){
// 創(chuàng)建實(shí)例
}
return instance;
}
}
如果合法的話,那么對(duì)于每種實(shí)例化類型,都需要有一個(gè)對(duì)應(yīng)的靜態(tài)變量和方法。但由于類型擦除,Singleton類型只有一份,靜態(tài)變量和方法都是類型的屬性,且與類型參數(shù)無關(guān),所以不能使用泛型類類型參數(shù)。
但是,對(duì)于靜態(tài)方法,它可以是泛型方法,可以聲明自己的類型參數(shù),這個(gè)參數(shù)與泛型類的類型參數(shù)是沒有關(guān)系的。
6、子類繼承父類泛型
注意子類繼承泛型的注意事項(xiàng): 可以有四種方式,可以按需實(shí)現(xiàn),或者定義子類自己的泛型等
public class SubClass {
public abstract class Father<T1,T2> { //注意實(shí)際過程中一般定義為抽象的父類
T1 age;
public abstract void test(T2 name); //抽象方法
}
//1)全部保留
class C1<T1,T2,A,B> extends Father<T1,T2>{ //除了繼承父類,可以自己"加""富二代"(不是負(fù)二代)
@Override
public void test(T2 name) {
// this.age --> T1類型
}
}
//2)部分保留
class C2<T2,A,B> extends Father<Integer,T2>{
@Override
public void test(T2 name) {
// this.age --> Integer類型
}
}
//不保留: -->按需實(shí)現(xiàn)
class C3<A,B> extends Father<Integer,String>{
@Override
public void test(String name) { //注意這里是String 不是T2
// this.age --> Integer類型
}
}
//2)沒有類型 : 擦除 (類似于Object)//相當(dāng)于
class C4<A,B> extends Father { //相當(dāng)于 class C4<A,B> extends Father<Object,Object>{}
@Override
public void test(Object name) { //注意這里是Object(完全沒有類型(擦除))
// this.age --> Object類型
}
}
}