1、基本應(yīng)用
Java泛型可以用在類、接口和方法上探入〗瓶祝基本使用請(qǐng)參考《on Java 8》.
2、類型擦除
? 泛型時(shí)Java 5以后添加進(jìn)去的新症,為了兼容以前的版本步氏,相對(duì)于其他泛型語(yǔ)言,Java用特別的方法實(shí)現(xiàn)了泛型徒爹,這實(shí)現(xiàn)了Java向上兼容的功能荚醒,但同時(shí)也限制了Java泛型的功能。這個(gè)特別方法就是類型擦除了隆嗅。
? 類型擦除并不是Java語(yǔ)言特性界阁,它是通過編譯器來實(shí)現(xiàn)泛型的。編譯器在做靜態(tài)檢測(cè)時(shí)胖喳,通過一系列的處理來保證泛型實(shí)現(xiàn)泡躯,在靜態(tài)類型檢查之后,程序中的每個(gè)泛型類型都將被擦除丽焊,用非泛型的第一個(gè)邊界替換它(可能會(huì)有多個(gè)邊界)较剃。因此,在泛型類/接口/方法里面技健,你并不能得到關(guān)于類型參數(shù)的具體信息写穴,你能知道的是類型參數(shù)的表示符和他的邊界類型(默認(rèn)邊界時(shí)Object)。例如List<T>被擦除為L(zhǎng)ist雌贱,接口/類中的每個(gè)泛型變量/參數(shù)/返回值類型都被擦除到非泛型邊界啊送,默認(rèn)邊界為Object,List<T>沒有上界欣孤,被擦除為Object馋没。所以,ArrayList<String>和ArrayList<Integer>是一種類型降传。他們都被擦除為原始類型List篷朵。
? 類型的擦除的代價(jià)是巨大的。泛型類型不能用于顯式引用運(yùn)行時(shí)類型的操作婆排,如強(qiáng)制轉(zhuǎn)換款票、instanceof操作和new表達(dá)式。因?yàn)樗嘘P(guān)于參數(shù)的類型信息都丟失了泽论。在編寫泛型代碼時(shí)艾少,你只知道有這么一個(gè)類型參數(shù),以及這個(gè)類型參數(shù)的類型上界是什么翼悴,并不知道這個(gè)參數(shù)的具體信息缚够。在使用泛型類時(shí)幔妨,您必須不斷地提醒自己,你只是看起來好像知道有關(guān)參數(shù)的具體信息而已谍椅。
? 比如:ArrayList<String> list = new ArrayList<>()误堡,在使用list時(shí),你好像是知道它是一個(gè)String數(shù)組雏吭,而泛型語(yǔ)法也在強(qiáng)烈暗示你锁施,在整個(gè)類的各個(gè)地方,類型E都被替換了杖们。但是事實(shí)并不是如此悉抵。在ArrayList類的編寫過程中,你實(shí)際上知道的只是摘完,有個(gè)參數(shù)類型是E姥饰,他的類型上界是Object。
? 那么問題來了孝治,既然在運(yùn)行時(shí)ArrayList<String>的類型信息被擦除了列粪,那么我們?cè)谟胓et方法時(shí),得到對(duì)象時(shí)一個(gè)String谈飒,不用cast岂座,且我們用set方法時(shí),參數(shù)必須時(shí)一個(gè)字符串杭措,或者能調(diào)用toString方法的對(duì)象呢费什。下面我們先探討另外一個(gè)問題。泛型的邊界問題瓤介。
3、泛型的出入口
? 泛型類型可以時(shí)無任何意義的字符赘那,比如E刑桑。在靜態(tài)檢測(cè)后,類型E就被擦除為非泛型上界了募舟,所以在運(yùn)行時(shí)祠斧,時(shí)無法知道泛型的具體信息的。
import java.util.*;
public class ListMaker<T> {
private Class<T> kind;
@SuppressWarnings("unchecked")
T[] createArray(int size) {
return (T[])Array.newInstance(kind, size);
}
List<T> createList() { return new ArrayList<>(); }
public static void main(String[] args) {
ListMaker<String> stringMaker = new ListMaker<>();
String[] stringArray = stringMaker.createArray(9);
List<String> stringList = stringMaker.createList();
}
}
? 上面的代碼段中拱礁,T會(huì)被編譯器擦除琢锋,Class<T>最終存儲(chǔ)的只是Class,new ArrayList<>()變?yōu)閚ew ArrayList()呢灶。
? 在createArray()方法中吴超,Array.newInstance()方法不知道T的任何信息,所以在運(yùn)行時(shí)鸯乃,并不能產(chǎn)生T類型的數(shù)組鲸阻,所以需要轉(zhuǎn)cast。
? createList方法中,盡管最終鸟悴,new ArrayList<>()變?yōu)閚ew ArrayList()陈辱,但是如果我們把new ArrayList<>()直接更換位new ArrayList(),編譯器就會(huì)給出警告细诸。
? 所以類型T不是沒有意義的沛贪,那么它的意義在哪呢?看下面代碼震贵。
import java.util.*;
import java.util.function.*;
import onjava.*;
public class FilledList<T> extends ArrayList<T> {
FilledList(Supplier<T> gen, int size) {
Suppliers.fill(this, gen, size);
}
public FilledList(T t, int size) {
for(int i = 0; i < size; i++)
this.add(t);
}
public static void main(String[] args) {
List<String> list = new FilledList<>("Hello", 4);
System.out.println(list);
// Supplier version:
List<Integer> ilist = new FilledList<>(() -> 47, 4);
System.out.println(ilist);
}
}
? 在main方法中利赋,我們?cè)趧?chuàng)建FilledList的同時(shí)往list里面添加對(duì)象,雖然編譯器并不知道關(guān)于T的任何東西屏歹,但是編譯器在編譯期間能確保放到FilledList里面的類型時(shí)T隐砸。所以,雖然在方法/類/接口里面T的相關(guān)信息被擦除了蝙眶,編譯器仍然可以確保方法或類中使用類型的方式的內(nèi)部一致性季希。換句話說,編譯器能保證泛型在其作用域內(nèi)使用方式一致性幽纷。泛型作用域式塌,如果聲名在接口/類上,那么就是在類和類方法的內(nèi)部友浸,如果是靜態(tài)方法峰尝,那就是靜態(tài)方法內(nèi)部。
? 擦除雖然刪除了方法主體中的類型信息收恢,但是編譯器能保證泛型作用域內(nèi)使用方式的一致性武学,那么泛型進(jìn)出作用域時(shí),編譯器是這么保證它的一致性呢伦意?接口/類/方法運(yùn)行時(shí)邊界:泛型對(duì)象進(jìn)入和離開主體的點(diǎn)火窒。
? 接口/類的進(jìn)出點(diǎn):field的取值和賦值,方法的參數(shù)和返回值驮肉。
? 方法的進(jìn)出口:參數(shù)和返回值熏矿。
? 編譯器在編譯時(shí)執(zhí)行類型檢查并插入類型轉(zhuǎn)換代碼。這也是編譯器保證泛型類/方法內(nèi)外泛型使用一致性的方式离钝。
下面的代碼:
public class GenericHolder2<T> {
// 靜態(tài)變量不能使用泛型
// public static T test;
public T genericBound;
private T obj;
public void set(T obj) { this.obj = obj; }
public T get() { return obj; }
public static void main(String[] args) {
GenericHolder2<String> holder =
new GenericHolder2<>();
holder.set("Item");
String genericBound = holder.genericBound;
String s = holder.get();
}
}
用javap命令反編譯上面代碼的字節(jié)碼:
PS D:\Study\githup\helloWorld\sourceCode\testMaven\onjava8\src\main\java\generics> javap -s -c -private GenericHolder2
警告: 二進(jìn)制文件GenericHolder2包含generics.GenericHolder2
Compiled from "GenericHolder2.java"
public class generics.GenericHolder2<T> {
public T genericBound;
descriptor: Ljava/lang/Object;
private T obj;
descriptor: Ljava/lang/Object;
public generics.GenericHolder2();
descriptor: ()V
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void set(T);
descriptor: (Ljava/lang/Object;)V
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field obj:Ljava/lang/Object;
5: return
public T get();
descriptor: ()Ljava/lang/Object;
Code:
0: aload_0
1: getfield #2 // Field obj:Ljava/lang/Object;
4: areturn
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
Code:
0: new #3 // class generics/GenericHolder2
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #5 // String Item
11: invokevirtual #6 // Method set:(Ljava/lang/Object;)V
14: aload_1
15: getfield #7 // Field genericBound:Ljava/lang/Object;
18: checkcast #8 // class java/lang/String
21: astore_2
22: aload_1
23: invokevirtual #9 // Method get:()Ljava/lang/Object;
26: checkcast #8 // class java/lang/String
29: astore_3
30: return
}
? 我們可以看到票编,發(fā)編譯后,類變量T的descriptor是Ljava/lang/Object卵渴,即他是引用類型Object慧域;方法set的descriptor是(Ljava/lang/Object;)V,即參數(shù)是object浪读,無返回值吊趾;方法get的descriptor是()Ljava/lang/Object宛裕,無參數(shù),返回object類型论泛。在編譯階段揩尸,會(huì)做類型檢測(cè),如果類內(nèi)部T的使用方式不一致屁奏,將報(bào)錯(cuò)岩榆。
? 既然變量genericBound,get方法返回的是object類型坟瓢,那我們?cè)谑褂玫臅r(shí)候勇边,返回值的接受類型又怎么變?yōu)檫\(yùn)行時(shí)指定的類型呢,在main方法中我們可以看到折联,在getfield和invokevirtual(get)命名后都緊跟著類型轉(zhuǎn)換命令粒褒,checkcast,這個(gè)時(shí)編譯階段編譯器自動(dòng)添加的诚镰。但是set方法調(diào)用呢奕坟,我們并沒有發(fā)現(xiàn)set方法調(diào)用時(shí)又上面特殊操作保證他是String啊清笨?其實(shí)月杉,這個(gè)工作時(shí)在編譯階段類型檢測(cè)所作的事情,類型檢測(cè)保證了set方法的參數(shù)時(shí)object抠艾,所以反編譯后就不需要特殊處理了苛萎。
? 利用類型檢測(cè),編譯器保證了方法調(diào)用參數(shù)等類入口的泛型一致性检号,以及類內(nèi)部泛型使用方式的一致性腌歉。利用cast自動(dòng)插入,保證了方法調(diào)用返回值等泛型出口的泛型一致性齐苛。
4翘盖、泛型的補(bǔ)償
? 在類/方法內(nèi)部,泛型擦除了方法信息脸狸,這就限制了泛型的使用最仑。比如藐俺,不能instanceof操作炊甲,不能使用new,不能創(chuàng)建數(shù)組(可以聲名泛型數(shù)組)欲芹,這些都可以通過適當(dāng)?shù)姆椒ㄟM(jìn)行彌補(bǔ)卿啡。
? instanceOf可以用isInstance()方法替代。
? new的替代方式:
- 利用Class對(duì)象的newInstance()方法菱父,該方法要保證class有無參構(gòu)造器
- 提供一個(gè)工廠對(duì)象(supplier)颈娜,利用lamda表達(dá)式的::new
? 數(shù)組的創(chuàng)建方式:
- 用List替代
- (T[])new Object[sz]剑逃。內(nèi)部維護(hù)T[],創(chuàng)建一個(gè)Object[]數(shù)組進(jìn)行強(qiáng)轉(zhuǎn)官辽。但是這種方法如果把數(shù)組暴露給外部蛹磺,接受對(duì)象如果不是Object[],會(huì)產(chǎn)生ClassCastException同仆,因?yàn)楦笇?duì)象時(shí)不能賦值給子對(duì)象的萤捆,這條規(guī)則同樣適用于數(shù)組。所以此方法只使用數(shù)組只是內(nèi)部使用的情況俗批。為什么(T[])new Object[sz]這里可以強(qiáng)轉(zhuǎn)俗或,用其他數(shù)組接收卻不可以,因?yàn)門最終被擦除了岁忘,變?yōu)榱薕bject辛慰,所以實(shí)際上(T[])new Object[sz]等價(jià)于(Object[])new Object[sz]。
- new Object[sz]干像,內(nèi)部維護(hù)的數(shù)組就是Object[]帅腌,在獲取數(shù)組原始后進(jìn)行類型轉(zhuǎn)換,這個(gè)方法需要在每個(gè)獲取數(shù)組元素的地方進(jìn)行強(qiáng)轉(zhuǎn)蝠筑。這個(gè)方法暴露數(shù)組對(duì)象給外部的時(shí)候只能用Object[]接收狞膘,依然不能轉(zhuǎn)換為其他對(duì)象。將數(shù)組內(nèi)部處理為Object[]而不是T[]的優(yōu)點(diǎn)是什乙,您不太可能忘記數(shù)組的運(yùn)行時(shí)類型挽封,從而意外地引入錯(cuò)誤(盡管在運(yùn)行時(shí)可以快速檢測(cè)到大多數(shù)(也許所有)此類bug)。
- (T[])Array.newInstance(type, sz);這個(gè)是創(chuàng)建泛型數(shù)組的較好的選擇臣镣。創(chuàng)建的數(shù)組就是實(shí)際類型的數(shù)組辅愿,不必?fù)?dān)心類型轉(zhuǎn)換,還可以將數(shù)組暴露給外部忆某,并用真正類型數(shù)組接收点待。
5、邊界
? 邊界是將泛型參數(shù)約束到一個(gè)子類型弃舒,然后可以使用被約束的子類型的功能癞埠。
package generics;// generics/BasicBounds.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
interface HasColor {
java.awt.Color getColor();
}
class WithColor<T extends HasColor> {
T item;
WithColor(T item) { this.item = item; }
T getItem() { return item; }
// The bound allows you to call a method:
java.awt.Color color() { return item.getColor(); }
}
class Coord {
public int x, y, z;
}
// This fails. Class must be first, then interfaces:
// class WithColorCoord<T extends HasColor & Coord> {
// Multiple bounds:
class WithColorCoord<T extends Coord & HasColor> {
T item;
WithColorCoord(T item) { this.item = item; }
T getItem() { return item; }
java.awt.Color color() { return item.getColor(); }
int getX() { return item.x; }
int getY() { return item.y; }
int getZ() { return item.z; }
}
interface Weight {
int weight();
}
// As with inheritance, you can have only one
// concrete class but multiple interfaces:
class Solid<T extends Coord & HasColor & Weight> {
T item;
Solid(T item) { this.item = item; }
T getItem() { return item; }
java.awt.Color color() { return item.getColor(); }
int getX() { return item.x; }
int getY() { return item.y; }
int getZ() { return item.z; }
int weight() { return item.weight(); }
}
class Bounded extends Coord implements HasColor, Weight {
@Override
public java.awt.Color getColor() { return null; }
@Override
public int weight() { return 0; }
}
public class BasicBounds {
public static void main(String[] args) {
Solid<Bounded> solid = new Solid<>(new Bounded());
solid.color();
solid.getY();
solid.weight();
}
}
我們將Solid類進(jìn)行反編譯如下:
PS D:\Study\githup\helloWorld\sourceCode\testMaven\onjava8\src\main\java\generics> javap -c -s -private Solid
警告: 二進(jìn)制文件Solid包含generics.Solid
Compiled from "BasicBounds.java"
class generics.Solid<T extends generics.Coord & generics.HasColor & generics.Weight> {
T item;
descriptor: Lgenerics/Coord;
generics.Solid(T);
descriptor: (Lgenerics/Coord;)V
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: aload_1
6: putfield #2 // Field item:Lgenerics/Coord;
9: return
T getItem();
descriptor: ()Lgenerics/Coord;
Code:
0: aload_0
1: getfield #2 // Field item:Lgenerics/Coord;
4: areturn
java.awt.Color color();
descriptor: ()Ljava/awt/Color;
Code:
0: aload_0
1: getfield #2 // Field item:Lgenerics/Coord;
4: checkcast #3 // class generics/HasColor
7: invokeinterface #4, 1 // InterfaceMethod generics/HasColor.getColor:()Ljava/awt/Color;
12: areturn
int getX();
descriptor: ()I
Code:
0: aload_0
1: getfield #2 // Field item:Lgenerics/Coord;
4: getfield #5 // Field generics/Coord.x:I
7: ireturn
int getY();
descriptor: ()I
Code:
0: aload_0
1: getfield #2 // Field item:Lgenerics/Coord;
4: getfield #6 // Field generics/Coord.y:I
7: ireturn
int getZ();
descriptor: ()I
Code:
0: aload_0
1: getfield #2 // Field item:Lgenerics/Coord;
4: getfield #7 // Field generics/Coord.z:I
7: ireturn
int weight();
descriptor: ()I
Code:
0: aload_0
1: getfield #2 // Field item:Lgenerics/Coord;
4: checkcast #8 // class generics/Weight
7: invokeinterface #9, 1 // InterfaceMethod generics/Weight.weight:()I
12: ireturn
}
? 可以看到T已經(jīng)被擦除到了Coord。而在類中聋呢,可以使用Coord/HasColor/Weight的屬性和方法了苗踪。但是同時(shí),你使用Solid的時(shí)候削锰,他的泛型參數(shù)必須是Coord/HasColor/Weight的共同子類通铲。這就是邊界的限制。
? 可是T被擦除到了Coord器贩,為什么可以使用HasColor/Weight的屬性和方法呢颅夺?在反編譯代碼中可以看到朋截,在調(diào)用HasColor和Weight的時(shí)候,編譯器插入了強(qiáng)轉(zhuǎn)代碼吧黄,把Coord強(qiáng)轉(zhuǎn)為了HasColor/Weight部服。
上面的類可以利用繼承重寫,簡(jiǎn)化相關(guān)代碼:
class HoldItem<T> {
T item;
HoldItem(T item) { this.item = item; }
T getItem() { return item; }
}
class WithColor2<T extends HasColor> extends HoldItem<T> {
WithColor2(T item) { super(item); }
java.awt.Color color() { return item.getColor(); }
}
class WithColorCoord2<T extends Coord & HasColor> extends WithColor2<T> {
WithColorCoord2(T item) { super(item); }
int getX() { return item.x; }
int getY() { return item.y; }
int getZ() { return item.z; }
}
class Solid2<T extends Coord & HasColor & Weight>
extends WithColorCoord2<T> {
Solid2(T item) { super(item); }
int weight() { return item.weight(); }
}
public class InheritBounds {
public static void main(String[] args) {
Solid2<Bounded> solid2 =
new Solid2<>(new Bounded());
solid2.color();
solid2.getY();
solid2.weight();
}
}
6拗慨、通配符
? 我們指定在數(shù)組中饲宿,子類型的數(shù)組Sub[]是可以賦值給父類型的數(shù)組Sup[]。因?yàn)镾ub是Sup的子類胆描,而數(shù)組是有他的元素的具體信息的瘫想,比如Sub[]數(shù)組知道自己存放的元素就是Sub。子類型可以放到一個(gè)父類型數(shù)組中昌讲,不管父數(shù)組的實(shí)際類型是啥国夜,但是如果父類型的實(shí)際類型時(shí)子類型2,你如果此時(shí)把子類型1放到父類型數(shù)組中短绸,在編譯器沒事车吹,在運(yùn)行時(shí)會(huì)報(bào)錯(cuò)。數(shù)組的這個(gè)規(guī)則如果濫用醋闭,會(huì)導(dǎo)致一些錯(cuò)誤窄驹。泛型可以解決這個(gè)問題。
? 但是在泛型中证逻,因?yàn)椴脸植海盒皖愂遣恢婪盒偷木唧w信息的。所以囚企,雖然Sub時(shí)Sup的子類丈咐,但是Sub的集合ArrayList<Sub>不是Sub的集合ArrayList<Sup>的子類,所以ArrayList<Sub>不能"向上轉(zhuǎn)型"為ArrayList<Sup>龙宏!因?yàn)榉盒皖愒趈ava中只有一種類型棵逊,那就是原型ArrayList,ArrayList<Sub>和ArrayList<Sup>都會(huì)被擦除為ArrayList银酗。所以這樣的賦值操作是不可以的辆影,因?yàn)锳rrayList并不知道泛型參數(shù)的實(shí)際類型的具體信息。
? 但是黍特,有時(shí)候我們又需要集合的"向上轉(zhuǎn)型"這種操作蛙讥,來建立兩類集合的這種關(guān)系。比如接收一個(gè)方法返回值的時(shí)候衅澈。泛型通過另一個(gè)方法實(shí)現(xiàn)了這個(gè)功能键菱,那就是參數(shù)通配符"?”谬墙。
? extends T
? List<? extends Fruit>今布,您可以將其讀作“從水果繼承的任何類型的列表”经备。然而,這并不意味著這個(gè)清單里會(huì)有任何種類的水果部默。通配符指的是一個(gè)確定的類型侵蒙,所以它的意思是list引用沒有指定的某個(gè)特定類型”。因此傅蹂,被分配的列表必須包含某些特定類型纷闺,如Fruit或Apple,但是list并不關(guān)心它是由那種子類"向上轉(zhuǎn)型"過來的份蝴。
? 那么你用List<? extends Fruit>接收到一個(gè)List以后犁功,你能干什么呢?前面說過婚夫,通配符是為了解決數(shù)組“向上轉(zhuǎn)型”的問題的浸卦。這個(gè)是什么問題呢?看下面的代碼:
List<Orange> oranges = new ArrayList<>();
List<? extends Fruit> flist = oranges;
// flist.add(new Apple()) 編譯錯(cuò)誤
Orange orange = oranges.get(0);
? 你現(xiàn)在知道的是flist中保存的是一種Fruit的子類型案糙,但是不知道是哪一種類型限嫌。如果第三行代碼,向flist添加一個(gè)apple成功时捌,那么第四行代碼orange實(shí)際上拿到的就是一個(gè)apple怒医,這個(gè)就會(huì)造成類型轉(zhuǎn)換異常,把list換位數(shù)組奢讨,就是我說的數(shù)組問題稚叹。為了解決這個(gè)異常,編譯器直接再編譯階段就給出了錯(cuò)誤提示拿诸。
? 所以List<? extends Sup>禁止你往作用域中添加null之外的任何元素入录。這里注意,flist.add(new Apple())無法編譯的原因是ArrayList中佳镜,add方法的參數(shù)是泛型僚稿。
? 也就是說,如果一個(gè)泛型類蟀伸,你指定他的泛型是? extends Sup蚀同,那么在任何泛型作用域內(nèi),任何泛型入口都是被編譯器禁止的啊掏。
? 但是你可以從flist中獲取一個(gè)元素蠢络,獲取的元素類型是fruit。
? 從泛型擦除的概念理解:根據(jù)擦除的規(guī)則迟蜜,? extends T會(huì)被查出到第一個(gè)邊界刹孔,也就是T。所以娜睛,在進(jìn)出口邊界髓霞,泛型會(huì)被T類型所替換卦睹。進(jìn)行相應(yīng)強(qiáng)轉(zhuǎn)和類型檢測(cè)。在出口處方库,因?yàn)? extends T是T的一個(gè)子類型结序,所以強(qiáng)轉(zhuǎn)為T是沒問題的。但是在入口處進(jìn)行檢測(cè)時(shí)纵潦,編譯器知道泛型類要求T的一種子類型徐鹤,但不知道具體時(shí)哪一種子類型,所以無論你傳什么邀层,為了安全起見返敬,他都會(huì)拒絕你傳入。
? 所以寥院,有一種說法救赐,? extends Sup是消費(fèi)者泛型。
? Supper T
? 那么我們?cè)趺聪蛲ㄅ浞盒屠锩尜x值呢只磷?與extends相對(duì)的经磅,泛型中也有supper關(guān)鍵字,<? super MyClass>或者<? super T>钮追,意思是一個(gè)類型预厌,他是MyClass/T的父類,這個(gè)類型可以通過泛型入口進(jìn)入泛型作用域元媚。這樣轧叽,泛型在往里面增加值的時(shí)候,只要保證增加的都是MyClass/T的子類就行刊棕。
? 根據(jù)泛型擦除規(guī)則炭晒,? Supper T被擦除為T。所以甥角,在進(jìn)出口邊界网严,泛型會(huì)被T類型所替換。進(jìn)行相應(yīng)強(qiáng)轉(zhuǎn)和類型檢測(cè)嗤无。在出口處震束,因?yàn)? super T是T的一個(gè)父類型,編譯器并不知道T是什么類型当犯,為了安全起見垢村,所以將T強(qiáng)轉(zhuǎn)為了Object。在入口處進(jìn)行檢測(cè)時(shí)嚎卫,編譯器知道泛型類要求T的一種父類型嘉栓,但不知道具體時(shí)哪一種父類型,為了安全起見,它要求你傳一個(gè)大范圍的類型侵佃,就是T或者T的子類型麻昼。
? 所以,有一種說法趣钱,? extends Sup是生產(chǎn)者泛型。
?
? 這個(gè)通配符使用效果和使用原泛型效果一樣胚宦,即:ArrayList<?>等價(jià)于ArrayList首有。
? 它有一個(gè)作用就是泛型類型推斷轉(zhuǎn)換,就是說你如果將一個(gè)原類型類實(shí)例傳入一個(gè)參數(shù)為無界通配符的方法中枢劝,那么編譯器可以推斷出原類型的泛型的實(shí)際類型井联,這個(gè)方法可以用具體類型調(diào)用另一個(gè)泛型方法。
? 這個(gè)功能點(diǎn)沒做深究您旁,有機(jī)會(huì)補(bǔ)充烙常。
7、泛型缺點(diǎn):
- 不能使用基本類型鹤盒。
- 不能同時(shí)實(shí)現(xiàn)兩個(gè)相同的泛型接口蚕脏,即使他們的泛型不同。
- 不能使用instanceof
- 兩個(gè)方法侦锯,除了參數(shù)的泛型類型不同外驼鞭,其他都相同,他們的方法簽名相同尺碰,不算重載挣棕。
8、總結(jié)
? 泛型擦除有兩種亲桥,一種是定義擦除洛心,一種是使用擦除。這兩種擦除都又都包含兩種:邊界擦除和非邊界擦除题篷。
? 非邊界查出是在使用的時(shí)候直接指定了泛型類的具體類型词身,不使用extend和super關(guān)鍵字,編譯器在你使用泛型類的地方將泛型擦除為你指定的類型番枚。
? 邊界擦除是指含有extend和super關(guān)鍵字的泛型的擦除偿枕,extend和supper關(guān)鍵字為了約束泛型的類型并使用被約束類型的功能。邊界擦除將泛型擦除為第一個(gè)邊界類型户辫。
? 知道了邊界查出和非邊界擦除渐夸,我們?cè)倩貋砜炊x擦除和使用擦除。
? 定義擦除:是指編譯器在編譯定義的泛型類時(shí)渔欢,將泛型類總的泛型(邊界和非邊界)的類型信息擦除墓塌,并做相應(yīng)的類型檢測(cè)。
? 使用擦除:是指在使用泛型類的時(shí)候,將你指定的放心的具體類型(邊界和非邊界)查出苫幢,再在泛型類的入口做相應(yīng)的類型檢測(cè)访诱,在出口做強(qiáng)轉(zhuǎn)。
? 使用擦除中有一種比較特殊的情況韩肝,那就是使用通配符"?"的使用触菜,通配符只用在使用泛型使用。? extend T哀峻,這種通配符邊界擦除涡相,會(huì)在泛型出口的所有地方將泛型擦除為T,并強(qiáng)轉(zhuǎn)剩蟀,在入口處進(jìn)行類型檢測(cè)的時(shí)候直接報(bào)錯(cuò)催蝗。? super T育特,會(huì)在泛型出口的所有地方將泛型擦除為Object丙号,并強(qiáng)轉(zhuǎn)為Object,在入口做類型檢測(cè)缰冤,保證入口時(shí)T或T子類型犬缨。?會(huì)在泛型出口的所有地方將泛型擦除為Object棉浸,并強(qiáng)轉(zhuǎn)為Object遍尺,在入口在入口做類型檢測(cè),保證他是Object類型即可涮拗,所以乾戏?和不適用泛型的原類型時(shí)相同的效果。
參考資料
《on Java 8》 Bruce Eckel三热。注:Java編程思想第五版鼓择。