以下都是個(gè)人理解油啤,若有錯(cuò)誤伴澄,請(qǐng)多多批評(píng)
1. 例子
先定義如下繼承關(guān)系
class C{}
class A {
public void say() {
System.out.println("I am A");
}
}
class B extends A {
public void say() {
System.out.println("I am B");
}
}
早期版本的Java代碼(1):
List list = new ArrayList();
list.add(123);
list.add("abc");
//o到底是什么類型真仲?不清楚....可能我們本意是放入數(shù)字喇颁,但不知被誰放入了字符串寄月,容易引發(fā)bug
Object o = list.get(0);
之后引入了泛型,代碼(2)變成了這樣:
List<Integer> list1 = new ArrayList();
list1.add(123);
list1.add("abc"); // error ! error !
//此時(shí)o是Integer
Integer o = list1.get(0);
很多時(shí)候我們想定義一種List无牵,這種List里只能包含一個(gè)繼承體系內(nèi)的對(duì)象漾肮,比如只能包含Number,代碼(3)如下:
//list3中只能放Number的子類或Number本身
List<? extends Number> list3 = new ArrayList();
//下面這句為何會(huì)報(bào)錯(cuò)茎毁?Integer是Number的子類呀克懊。
//List<? extends Number>實(shí)際上聲明了一個(gè)list忱辅,這個(gè)list中可以放入Number的某一個(gè)子類型,Integer或Long或其他均可谭溉;但是并不能直接向里面add墙懂;
//試想,如果add(Integer)可以扮念,那么add(Long)肯定也行损搬,這樣和早期沒有泛型的代碼有何區(qū)別?
//換句話說柜与,此時(shí)list里的類型并不能確定巧勤,所以不能add具體的類型進(jìn)去.
list3.add(new Integer(1)); //error !
//get()是可以的,因?yàn)槔锩娴念愋椭辽倌艽_定是Number的弄匕。
Number number = list3.get(0);
List<? super B> list4 = new ArrayList();
//放入B肯定是正確的
list4.add(new B());
//此時(shí)的list4可以放入B的某一個(gè)父類颅悉,但不能確定是A
list4.add(new A()); //error !
Object object = list4.get(0);//類型又不確定了
如上所述迁匠,List<? extends Number>和List<? super B>都只是對(duì)類型范圍進(jìn)行了限定剩瓶,list中具體是哪種類型是不確定的。在java中城丧,若想自由的add和get延曙,list中的類型必須如代碼(2)所示的那樣,確定類型亡哄。因此枝缔,這種用法一般會(huì)在方法中來限定方法參數(shù),代碼(4)如下:
//定義一個(gè)方法, 這個(gè)方法第一個(gè)參數(shù)接收一個(gè)list磺平,這個(gè)list里要么是T魂仍,要么是T的一個(gè)父類型
private <T> void addElement(List<? super T> list, T t){
list.add(t);
}
List<A> list2 = new ArrayList();//包含具體類型的list
//可以直接list2.add(new A())和list2.add(new B())
//確定類型的list傳入方法,
addElement(list2, new A());
addElement(list2, new B());
list2.get(0).say(); //I am A
list2.get(1).say(); //I am B
List<C> list3 = new ArrayList();
addElement(list3, new C());
2. 協(xié)變和逆變
簡單得說拣挪,協(xié)變是把子(窄)類型賦值給父(寬)類型擦酌,逆變則相反。
List list1 = new ArrayList(); // ok菠劝,多態(tài)的實(shí)質(zhì):父類的引用指向具體的一個(gè)子類實(shí)例
List<Number> list2 = new ArrayList(); // ok赊舶,此時(shí)左側(cè)可以有泛型,右側(cè)沒有泛型
List<Number> list21 = new ArrayList<Number>(); // ok
List<Number> list3 = new ArrayList<Integer>(); // error赶诊,泛型不支持協(xié)變
List<? extends Number> list4 = new ArrayList<Integer>(); // ok笼平,<? extends >使泛型也可以進(jìn)行協(xié)變
List<Integer> list5 = new ArrayList<Number>(); // error,泛型不支持逆變
List<? super Integer> list6 = new ArrayList<Number>(); // ok舔痪,<? super >使泛型支持了逆變
list6.add(new Integer(1)); // ok
list6.add(new Long(1)); // error
List<? super Number> list61 = new ArrayList<Number>(); // ok
list61.add(new Integer(1)); // ok
list61.add(new Float(1.2f)); // ok
//寓调?super AAA,只要是在AAA的繼承圖譜中锄码,子類或者父類都可以add
3. 泛型數(shù)組
首先數(shù)組是協(xié)變的
Number[] numbers = new Integer[1]; // ok
List<String>[] list = new ArrayList[1]; // ok
但是夺英,不能創(chuàng)建泛型數(shù)組
new ArrayList<String>[1] // error
List<?>[] lsa = new List<?>[10] // ok, 這時(shí)相當(dāng)于沒有泛型
Example
下面的代碼編譯沒問題晌涕,但是運(yùn)行期報(bào)錯(cuò)了
//List<String>[] lsa = new List<String>[10]; // error.
List<String>[] lsa = new List[10]; // ok,這不是泛型數(shù)組
Object[] oa = lsa; //移花接木痛悯,運(yùn)用協(xié)變性余黎,將lsa賦給父類型
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3)); // list中放入了Integer
oa[1] = li;
String s = lsa[1].get(0); // error: ClassCastException.
上面代碼的本意是創(chuàng)建一個(gè)只能裝入String的數(shù)組,但我們使用一點(diǎn)技術(shù)手段將Integer裝入载萌,運(yùn)行時(shí)就會(huì)出錯(cuò)惧财,這就是不能創(chuàng)建泛型數(shù)組的最主要原因。
采用通配符的方式是允許的扭仁。雖然這種方式是可以的垮衷,但是需要顯示轉(zhuǎn)換類型,違背了泛型設(shè)計(jì)的初衷:添加泛型的一個(gè)重要目的就是消除這種顯示的轉(zhuǎn)換斋枢,而這種代碼又必須添加帘靡。
List<?>[] lsa = new List<?>[10]; // ok
Object[] oa = lsa;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li;
Integer i = (Integer) lsa[1].get(0); // ok知给,需要強(qiáng)制轉(zhuǎn)換
System.out.println(i);
List<String> list = new ArrayList<>();
list.add("hahaha");
oa[2] = list;
String o1 = (String) lsa[2].get(0);
System.out.println(o1);
4. ?和T的區(qū)別
邏輯上List<?>可以看成是所有List<Integer>瓤帚,List<String>等的父類。T代表某種具體類型涩赢。很多時(shí)候二者是等價(jià)的戈次。
//下述兩個(gè)方法是相同的,不能同時(shí)出現(xiàn)在一個(gè)類中
//這種方式定義方法可以消除重復(fù)性
public void test(List<?> list){}
public <T> void test(List<T> list){}
5. instanceof
list instanceof ArrayList<Number> // error. 這樣使用時(shí)筒扒,可能是想判斷l(xiāng)ist是否是盛著Number的Arraylist怯邪,但是Number會(huì)被擦除掉,因此不能對(duì)確切的泛型使用
list instanceof ArrayList<?> // ok花墩,相當(dāng)與list instanceof ArrayList悬秉。其目的應(yīng)該是判斷l(xiāng)ist是否是Arraylist
6. 類型擦除
我們都知道,編譯后泛型會(huì)被擦除冰蘑,那我們怎樣在運(yùn)行期獲取這些泛型信息呢和泌?
Java 引入泛型擦除的原因是避免因?yàn)橐敕盒投鴮?dǎo)致運(yùn)行時(shí)創(chuàng)建不必要的類。
(1)如下例子祠肥,兩個(gè)class相同武氓,泛型的不同并沒有導(dǎo)致生成不同的Class類。
Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass();
System.out.println(c1 == c2); //true
(2)定義如下類仇箱,查看一下字節(jié)碼
class Generic_2<T>{
List<String> list = new ArrayList<String>(); //1
List<T> list2 = new ArrayList<T>(); //2
public void test(T t) {
List<Integer> list3 = new ArrayList<Integer>(); //3
list3.add(1); //4
}
}
運(yùn)行命令:java -v Generic_2.class县恕,下面為部分信息
java.util.List<java.lang.String> list; //1
descriptor: Ljava/util/List;
flags:
Signature: #13 // Ljava/util/List<Ljava/lang/String;>; //有泛型信息
java.util.List<T> list2; //2
descriptor: Ljava/util/List;
flags:
Signature: #15 // Ljava/util/List<TT;>; //有泛型信息
public void test(T); //test方法
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=2
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_2
8: aload_2
9: iconst_1
10: invokestatic #6 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
13: invokeinterface #7, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z //沒有泛型信息了
18: pop
19: return
LineNumberTable:
line 34: 0
line 35: 8
line 36: 19
LocalVariableTable:
Start Length Slot Name Signature
0 20 0 this Lcom/young/generic/Generic_2;
0 20 1 t Ljava/lang/Object;
8 12 2 list3 Ljava/util/List;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 20 0 this Lcom/young/generic/Generic_2<TT;>;
0 20 1 t TT;
8 12 2 list3 Ljava/util/List<Ljava/lang/Integer;>;
Signature: #32 // (TT;)V //有泛型信息
}
上述字節(jié)碼其實(shí)說明了泛型擦除后,其實(shí)只是擦除了Code屬性里字節(jié)碼指令相關(guān)的泛型信息剂桥,Signature這些元數(shù)據(jù)信息中其實(shí)依然保留了泛型信息忠烛,這就是我們可以通過反射獲取泛型信息的根本原因。
(3)定義了幾個(gè)類來獲取泛型信息:
class A<K,V>{}
class B extends A<String, Integer>{}
interface C<T>{}
class D implements C<String>{}
public static void main(String[] args){
//extends
Type type = B.class.getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) type;
for (Type type1 : parameterizedType.getActualTypeArguments()) {
System.out.println(type1);
}
//implements
ParameterizedType parameterizedType1 = (ParameterizedType) D.class.getGenericInterfaces()[0];//0代表只有一個(gè)接口
for (Type type1 : parameterizedType1.getActualTypeArguments()) {
System.out.println(type1);
}
//getTypeParameters()只能獲取聲明時(shí)的泛型名字
TypeVariable<? extends Class<? extends A>>[] typeParameters = new A().getClass().getTypeParameters();
for (TypeVariable<? extends Class<? extends A>> typeParameter : typeParameters) {
System.out.println(typeParameter.getTypeName());
}
}
/**output
class java.lang.String
class java.lang.Integer
class java.lang.String
K
V
**/
7. 幾個(gè)例子
Collections.java
// sort方法是既要從list中取权逗,也要把結(jié)果放入list美尸,這種即get又set的方法垒拢,必須用確定的類型T
public static <T extends Comparable<? super T>> void sort(List<T> list){...};
//fill方法只會(huì)set而不會(huì)get,所以要用super
public static <T> void fill(List<? super T> list, T obj) {...}
//這個(gè)方法只會(huì)get而不會(huì)set火惊,所以要用extends
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) {...}
本文參考了
http://swiftlet.net/archives/1950
https://www.cnblogs.com/lzq198754/p/5780426.html
https://my.oschina.net/lifany/blog/875769