泛型:
本內(nèi)容均屬原創(chuàng)胆胰,一個字一個字敲的,如果要轉(zhuǎn)載請注明出處:
http://www.reibang.com/p/2aa69dd9222d
寫這篇文章的初衷缚够,是對于大家遇到的幾道泛型(Generic)題目的不理解
那不妨我們在這里展開幔妨,好好討論一下泛型這個鬼東西。
為了更好的說明泛型這個內(nèi)容我會從以下幾個方向依次展開討論:
1谍椅、什么是泛型?
i误堡、什么是類型安全的
ii、泛型的語法格式
2雏吭、為什么需要泛型锁施?
3、泛型主要分類:
i杖们、泛型類
ii悉抵、泛型方法
iii、泛型接口
iv摘完、泛型構(gòu)造器
4姥饰、泛型的注意事項:
i、優(yōu)點: 確保數(shù)據(jù)安全性
a孝治、泛型中能使用基本數(shù)據(jù)類型
b列粪、不同類型的泛型對象能互相轉(zhuǎn)換
ii、反射對于泛型的影響
iii谈飒、泛型邊界問題
iv岂座、泛型的擦除:
5、jdk8增強的泛型類型推斷
6杭措、拓展jdk10增強的局部變量的類型推斷
1费什、什么是泛型?
什么是泛型呢?就是一個廣泛的類型,注意它首先是一個類型手素,其次因為廣泛鸳址,所以就是不明確類型。
所以我們說泛型就是一個不明確的類型泉懦。那如果類型不明確我們?nèi)绾问褂媚芈戎剩縿e著急我們往下繼學(xué)。
泛型是從jdk1.5之后引入的新特性祠斧。它不光增加了java的新的語法,同樣也改變了核心API中的許多類以及方法拱礁。常見的比如java.util.*
琢锋。通過泛型,我們可以創(chuàng)建類型安全的類呢灶、接口等吴超。
1-1:什么是類型安全的呢?
基于這個問題鸯乃,我會在第二塊中詳細闡述這個問題鲸阻。這里先不深入
1-2:泛型的語法格式
我們在后續(xù)的泛型分類中會詳細介紹泛型不同的創(chuàng)建方式和細節(jié)跋涣,這里我們要注意泛型的書寫方式!
通過一組<> 去定義泛型鸟悴。然后在<>中編寫任意的字母即可陈辱。但是不能以數(shù)字開頭。
我們通常會通過在<>中定義26個英文字母的大寫表示细诸。一般常見的字母為T沛贪,E,K,V等震贵。
例子:
<T> //定義一個泛型類型 T
<K,V> //定義了一個泛型 包含兩個泛型類型分別是K和V
2利赋、為什么需要泛型?
2-1:我們試想,如果沒有泛型猩系,假設(shè)在一下代碼中媚送,會出現(xiàn)什么問題?
測試代碼1:
List ls = new ArrayList();
ls.add("str");
ls.add(new Integer(12));
for(int i = 0;i<ls.size();i++){
Integer s = (Integer) ls.get(i);
System.out.println(s);
}
總結(jié):
以上代碼會出現(xiàn)問題寇甸,ClassCastException<類型轉(zhuǎn)換異常>塘偎。原因大家應(yīng)該都很清楚,那我們試想幽纷,如果沒有泛型式塌,所有對于數(shù)據(jù)的操作都會按照顯式
的方式進行轉(zhuǎn)換,所以我們說泛型在一定程度上幫助我們做到了確保數(shù)據(jù)安全性友浸。當然安全性我們還會通過其他示例來深入闡述峰尝。
測試代碼2:
public static void main(String[] args) {
List lists = new ArrayList();
lists.add("hello");
lists.add(12);
List<String> ls = new ArrayList<String>();
ls.add("hello");
ls.add(12);//the compile-time error
}
總結(jié):
以上代碼我們能夠很直觀的看出來,如果對于一個沒有聲明泛型的泛型的List集合來講收恢,可以往集合中添加任意類型(注意只能是引用類型)武学。而對于聲明類泛型類型為String的List集合而言只能添加Stirng對象,當要往List集合添加12時(注意伦意,這里的12會做自動裝箱)火窒,編譯時會報錯。很好幫我解決了數(shù)據(jù)驗證的問題驮肉。其實我們很難有場景要在一個集合中填充不同對象熏矿,對于后期來講這樣是不太方便的。大多數(shù)場景下我們還是填充的統(tǒng)一類型數(shù)據(jù)离钝。(當然有時候我們確實有添加不同對象的需求票编,我們在后續(xù)的例子中會展開討論)。
測試代碼3:此時泛型的行為時期
在這里我們將上述的代碼編譯之后卵渴,通過反編譯工具打開再次查看:
public static void main(String[] args)
{
List lists = new ArrayList();
lists.add("hello");
lists.add(Integer.valueOf(12));
List ls = new ArrayList();
ls.add("hello");
}
這里無論是lists集合還是ls集合慧域,在編譯完之后的我們發(fā)現(xiàn),泛型都不存在了浪读,而且12的填充確實調(diào)用了Integer.valueOf()進行了自動裝箱昔榴。所以我們說泛型是一個編譯器行為
辛藻。那其實這也就為我們通過反射在運行期動態(tài)往一個泛型集合中添加不同數(shù)據(jù)類型埋下伏筆。
3互订、泛型分類
接下來我會通過四個方向依次詳細說明泛型在不同情境下定義要遵守的一些規(guī)則吱肌。
3-1:泛型類:
3-1-1:定義普通泛型類
public class Test01 {
public static void main(String[] args) {
//create generic type Gen instance and assign
//generic type of Gen is Integer
//it`s use of autoboxing to encapsulate
//the value 12 within an Integer Object
Gen<Integer> g1 = new Gen<Integer>();
g1.t = 12;
g1.getT();
//jdk1.7 enhancement type inference
Gen<String> g2 = new Gen<>();
g2.t = "hello";
g2.getT();
//create generic type Gen instance and
//don`t assign generic type
Gen g3 = new Gen();
g3.t = new Date();
g3.getT();
}
}
//declared a generic type Gen
class Gen<T>{
T t;
public void getT(){
System.out.println(t.getClass().getName());
}
}
總結(jié)
在這里說明了一個Generic類->Gen,泛型類型是T,我們上文提到過屁奏,泛型就是一個類型岩榆,那么這里的T你不防理解為一個類型。并且為了獲取T類型方便一些坟瓢,我們將T類型也聲明為了一個成員變量勇边。我們在代碼中通過getT()方法獲取成員變量T的Class對象的名稱。其次我們在測試類創(chuàng)建了3個Gen的對象折联。
在第一個用例中:創(chuàng)建對象時指定類型為Integer粒褒,且通過給對象中的T類型賦值為12。我們發(fā)現(xiàn)獲取到的Class對象的名稱為Integer诚镰。
第二個用例中:創(chuàng)建對象時指定類型為String奕坟,且這里我們在對象創(chuàng)建中只通過<>
,而沒有在括號中給具體的類型清笨,這是jdk1.7中的增強類型推斷月杉,因為已經(jīng)聲明過時String類型,所以會自動推斷出Gen對象的T類型是String。同樣這里獲取到的Class對象名稱為String抠艾。
第三個用例中:創(chuàng)建對象時沒有指定泛型類型苛萎,我們這里直接賦值發(fā)型可以給T傳入任何Object類型。其實這就是典型的擦除检号。我們后續(xù)分享會逐一解開這個面紗腌歉。這里獲取到的泛型的name成為了Object。
總結(jié):在一個類聲明時通過<T>會指定泛型類型齐苛。創(chuàng)建對象時可以在<>指定具體的泛型類型翘盖。
如果不指定則經(jīng)過擦除之后,類型變?yōu)镺bject凹蜂。我們可以將g1Gen<Integer>和g2<String>
理解為一個Gen<T>類的一個特殊子類馍驯。這個子類是不存在。(注意只是這樣理解玛痊,方便我們后續(xù)
理解類型擦除)泥彤。
注意:創(chuàng)建泛型類型對象時,構(gòu)造器的類名還是原來的類名卿啡。不需要增加泛型聲明。不需要寫為Gen<T>菱父。
3-1-2:泛型類中的繼承
我們通過以下幾個小例子闡述颈娜,在創(chuàng)建泛型子類時的編寫問題:
class Gen<T>{
T t;
public void getT(){
System.out.println(t.getClass().getName());
}
}
//declared a subclass for Gen
//subclass don`t define generic tyoes, allow the definition of declared
class SubGen1 extends Gen{}
//allows subclass to specify the superclass specific generic type
class SubGen2 extends Gen<String>{}
//don`t allows superclass definition generic type
class SubGen3 extends Gen<T>{}//compile-time error
總結(jié):
1剑逃、定義子類繼承泛型類,不做任何泛型方法官辽,那么創(chuàng)建子類對象時泛型類中的類型自動轉(zhuǎn)為Object
2蛹磺、定義子類繼承泛型類,在父類中指定具體泛型類型同仆,那么創(chuàng)建子類對象時泛型類中的類型為指定類型
3萤捆、定義子類繼承泛型類,在父類中指定不具體的泛型類型俗批,那么是不允許的俗或。
3-2:泛型接口:
測試用例:
//define generic interface
interface List<E> extends Iterator<E>{
void add(E e);
Iterator<E> iterator();
}
//define generic interface
interface Iterator<E>{
E next();
boolean hasNext();
}
//define subclass for interface
class MyList implements List <String>{
@Override
public String next() {return null;}
@Override
public boolean hasNext() {return false;}
@Override
public void add(String e) {}
@Override
public Iterator<String> iterator() {return null;}
//test generic interface
public static void main(String[] args) {
MyList ls = new MyList();
ls.add("hehe");//execute add method can only add Stirng type
}
}
結(jié)論:
1、定義泛型接口時岁忘,實現(xiàn)類如果聲明時指定了泛型類型辛慰,那么后續(xù)調(diào)用時,在編譯階段只能使用該類型干像。
2帅腌、如果定義泛型接口時,實現(xiàn)類不指定接口的泛型類型麻汰,那么會報警告速客。
List is a raw type. References to generic type List<E> should be parameterized。說明當前泛型接口是一個 raw type.最好指定泛型是參數(shù)化的五鲫。
而且對于后續(xù)程序來講這樣也是不太可取的溺职。
包含泛型的類型,不論是類臣镣、子類辅愿、實現(xiàn)類、對象忆某。我們其實在理解層面上都可以認為是當前類点待、
實現(xiàn)類的一個邏輯子類。比如Gen<String>可以理解為一個Gen<Object>的子類弃舒,List<String>是List<T>的一個子類癞埠,但是這個是一個邏輯,在物理上不存在聋呢。只是為了方便理解苗踪。
3-3:泛型方法 jdk1.5支持
定義方法,完成功能削锰,將數(shù)組中的數(shù)據(jù)填充到集合中通铲。
測試用例:
public static void main(String[] args) {
Object[] obj = new Object[]{"hello",123.12,"java"};
ArrayList<Object> as = new ArrayList<>();
fillIntoColls(obj, as);
obj = new Object[]{"hello",123.12,"java"};
Collection<String> ass = new ArrayList<>();
//fic(Object[], Collection<Object>)
//not applicable for the arguments (Object[], ArrayList<String>)
fillIntoColls(obj, ass);//compile-time error
}
public static void fillIntoColls(Object[] objs,Collection<Object> cols){
for(Object obj:objs){
cols.add(obj);
}
}
結(jié)論:
雖然看著方法沒有問題,但是在編譯階段Collection<String>對象不能作為Collection<Object>的對象使用器贩。并且Collection<String>也不是Collection<Object>的子類型颅夺。
解決辦法:
1朋截、可以通過通配符以及上下限解決。后續(xù)分享中我們在討論吧黄。
2部服、泛型方法
測試用例:
public static void main(String[] args) {
Object[] obj = new Object[]{"hello",123.12,"java"};
ArrayList<Object> as = new ArrayList<>();
fillIntoColls(obj, as);
Collection<String> ass = new ArrayList<>();
String[] strs = new String[]{"hello","java"};
fillIntoColls(strs, ass);
}
public static <T> void fillIntoColls(T[] objs,Collection<T> cols){
for(T t:objs){
cols.add(t);
}
}
結(jié)論:
在方法中通過<>
聲明了一個泛型類型,在fillIntoColls方法中使用拗慨,將數(shù)組以及集合的類型都聲明為泛型類型廓八。當然也可以定義多個值。這里有個細節(jié)注意赵抢,因為是在static方法中聲明的剧蹂,所以當前的泛型方法中的泛型類型只能在當前方法中使用。如果是非static,也可以直接使用泛型類中聲明的昌讲,具體大家也可以參照java.util.ArrayList
国夜。編譯器會在這里根據(jù)實際參數(shù)推斷出類型的T的參數(shù),所以其實當你傳入的數(shù)組類型和集合的泛型類型不匹配短绸,調(diào)用方法時也會報錯车吹。
3-4:java7菱形語法以及泛型構(gòu)造器
3-4-1:java7增加的菱形語法:
測試用例:
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
l1.add("hello");
l2.add(12);
//compile-time error
l1.add(12);
l2.add("hello");
結(jié)論:jdk1.7之后增強了泛型的類型推斷。就是l1醋闭,l2對象創(chuàng)建時,指定了泛型的具體類型窄驹,那么構(gòu)造器后就無序指定完整的泛型信息。并且后續(xù)的add()方法對于l1而言只能添加String证逻,對于l2而言只能增加Integer對象乐埠。左右后面的兩行代碼都會在編譯期間報錯。
3-4-2:泛型構(gòu)造器:
注意:java允許構(gòu)造器聲明時可以使用泛型的囚企。這里的構(gòu)造器就變成了泛型構(gòu)造器丈咐。
測試用例:
public class Test {
public static void main(String[] args) {
new Gen(123);
new <String>Gen("123");
new <Integer>Gen(123.1);//compile-time error
Gen<Integer> g = new Gen<>(123); //compile-time error
}
}
class Gen{
//define constructor use generic
public <T> Gen(T t){
System.out.println(t);
}
}
結(jié)論:
泛型構(gòu)造器如果調(diào)用時不指定泛型類型,具體傳入的類型決定了泛型類型龙宏。其實經(jīng)過擦除之后就是Object
泛型構(gòu)造器調(diào)用時在new關(guān)鍵詞后加入泛型構(gòu)造器的泛型具體類型棵逊,那么傳入值的時候,會自動推斷出來是String類型(比如上述代碼的第2银酗、3行)辆影;但是如果聲明的類型和實際傳入的類型不一致,則會在編譯器報錯黍特。
這里千萬注意蛙讥,泛型構(gòu)造器使用時,書寫是new <類型> 類名();而泛型類創(chuàng)建對象時是new 類名<>();所以這一點千萬注意灭衷。但是這是泛型構(gòu)造器和泛型類只存在一個的情況下次慢。
泛型構(gòu)造器的坑
你覺的下面幾行代碼那幾行會報錯呢?
public class Test05 {
public static void main(String[] args) {
Gen<Integer> g1 = new Gen<>(123);
Gen<String> g2 = new <Integer>Gen(123);
Gen<String> g3 = new <Integer>Gen<String>(123);
Gen<String> g4 = new <Integer>Gen<>(123);
}
}
class Gen<E>{
//define constructor use generic
public <T> Gen(T t){
System.out.println(t);
}
}
結(jié)論:
- 第一行,指定了泛型類的類型是Integer,沒有指定泛型構(gòu)造器的具體類型经备,根據(jù)類型自動推斷的類型是Integer拭抬。
- 第二行,指定泛型構(gòu)造器的類型是Integer,傳入的實際類型是123 沒有問題侵蒙。
- 第三行,指定了泛型了您先給為String傅蹂,指定了泛型構(gòu)造器為Integer,傳入實參是123纷闺,沒問題。
- 第四行份蝴,如果聲明了泛型構(gòu)造器的實際類型犁功,那么這時候千萬注意,不能再使用jdk7的菱形語法了婚夫。這里會報錯浸卦。不支持這種寫法。其實就是不能寫了泛型構(gòu)造器而通過案糙;菱形語法讓編譯器推斷泛型類的具體類型限嫌。
4、注意事項:
4-1:確保數(shù)據(jù)的安全性
我們之前已經(jīng)寫過類似的實例时捌,比如在一個集合中插入數(shù)據(jù)怒医,通過泛型可以確保數(shù)據(jù)的安全性。避免我們顯示的強轉(zhuǎn)奢讨,另一方面稚叹,請看一下代碼,會有問題嗎拿诸?
實例代碼1
public class Test01 {
public static void main(String[] args) {
//create generic type Gen instance and assign
//generic type of Gen is Integer
Gen<Integer> g1;
//it`s use of autoboxing to encapsulate
//the value 12 within an Integer Object
g1 = new Gen<Integer>(12);
Gen<Integer> g2;
//there is a problem
g2 = new Gen<Double>(12.0);
//create eneric type Gen instance and type of Gen is String
Gen<String> g3 = new Gen<>("test generic");
//the compile-time error
//Type mismatch: cannot convert from Gen<String> to Gen<Integer>
g1 = g3;
}
}
//declared a generic type Gen
class Gen<T>{
T t;
public Gen(T t) {
this.t = t;
}
public void getT(){
System.out.println(t.getClass().getName());
}
}
結(jié)論:
以上代碼編譯會出錯扒袖,當然這中類型檢查其實也是泛型的優(yōu)點之一,可以確保類型安全亩码,當然從jdk1.7之后創(chuàng)建實例時可以通過類型推斷直接將對象的類型推斷
出來季率,不需要在后面繼續(xù)添加泛型類型了,因為兩個尖括號放在一起很像一個菱形蟀伸,所以也叫菱形語法蚀同。而且1.8之后對于泛型的類型推斷有進一步增強了。其次到最后的g3賦值給g1時啊掏,雖然類型都是Gen對象蠢络,但是泛型的具體類型不同,所以是無法正常賦值的迟蜜。
實例代碼2(相同的代碼不加入泛型)
public class Test01 {
public static void main(String[] args) {
//create non-generic type NonGen instance
//it`s use of autoboxing to encapsulate
//the value 12 within an Integer Object
NonGen ng1 = new NonGen(12);
//show the type of data used by ng1
ng1.show();
//get the value of ng1刹孔,a case is necessary
Integer in = (Integer) ng1.get();
System.out.println(in);
//create other NonGen instance and store a string in it;
NonGen ng2 = new NonGen("test non-generic");
//show the type of data used by ng2
ng2.show();
//get the value of ng2 again and case is necessary
String str = (String) ng2.get();
System.out.println(str);
//the compile-time is right and No syntax errors
//but there are semantic issues
ng1 = ng2;
in = (Integer) ng1.get();// run-time exception java.lang.ClassCastException:
System.out.println(in);
}
}
//declared a non-generic type Gen
class NonGen{
Object obj;//use object replace generic
public NonGen(Object obj) {
this.obj = obj;
}
public void show(){
System.out.println(obj.getClass().getName());
}
public Object get(){
return obj;
}
}
結(jié)論:
如果不加入泛型,這里我們要做大量的類型轉(zhuǎn)換。并且到最后我們看到的這個代碼髓霞,因為都是NonGen的實例對象卦睹,所以它們之間是可以互相賦值的。雖然在語法層面講沒有問題方库,但是語義上是有問題的结序,因為下面通過get方法獲取且強轉(zhuǎn)時,因為本身ng2存儲的是字符串對象纵潦,而這里賦值給了ng2對象變量徐鹤,再通過get()方法獲取時,獲取到的還是String對象邀层,強轉(zhuǎn)為Integer報錯返敬。所以泛型可以保證數(shù)據(jù)數(shù)據(jù)的安全性,將運行時異常變成了編譯時錯誤
本內(nèi)容為坑王社群原創(chuàng)內(nèi)容寥院,如有疑問:請咨詢微信:lukun0402
4-2:泛型中能使用基本數(shù)據(jù)類型
請看下面代碼:
public static void main(String[] args) {
//Syntax error, insert "Dimensions" to complete ReferenceType
List<int> ls = new ArrayList<>();
ls.add(123.123);
}
結(jié)論:
這里編譯出錯劲赠,不能這樣寫。需要插如的是一個引用類型秸谢。很多人回想那我需要插如一個int數(shù)據(jù)怎么辦呢凛澎?其實通過包裝類就可以完成。而且包裝類在某些時候確實要更加方便钮追,當然包裝類和基本數(shù)據(jù)類型的內(nèi)容要展開說预厌,還是有很多坑,我們下次再填補元媚。
4-3:不同類型的泛型對象能互相轉(zhuǎn)換
我們在泛型確保安全性上面已經(jīng)闡述過轧叽,這里在簡單強調(diào)一下:
//the compile-time error
//Type mismatch: cannot convert from Gen<String> to Gen<Integer>
g1 = g3;
統(tǒng)一類型(比如都是Gen泛型類的對象)不同的泛型類型(g1是泛型類型是String,g2是Integer類型)不是兼容類型刊棕,這點一定要注意炭晒。
結(jié)論:泛型能夠確保類型安全,其實用一句話簡單概括就是:只要通過使用泛型不存在編譯時的警告甥角,那么就不會出現(xiàn)運行時的ClassCastException;【注意編譯都不出警告网严。報錯更不會了,這個很好理解吧】
4-4: 反射對于泛型的影響:
測試用例:
public static void main(String[] args) throws Exception{
List<String> ls = new ArrayList<>();
//compile-time error not applicable for the arguments (Integer)
ls.add(new Integer(12));
//Gets the Class object filler value
Class clz = ls.getClass();
Method m = clz.getMethod("add",Object.class);
m.invoke(ls, new Integer(12));
//out 12 in this list
System.out.println(ls);
}
結(jié)論:
通過反射可以在運行期間填充泛型沒有指定的類型數(shù)據(jù)嗤无。因為泛型其實是一個編譯器行為震束,而反射是運行期行為。所以我們通過反射可以在運行期間動態(tài)的往集合中填充泛型未指定的數(shù)據(jù)類型当犯。但是如果直接填充Integer對象垢村,在編譯器就會報錯。
4-5:測試并不存在的泛型類型
測試用例:
public static void main(String[] args) {
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
System.out.println(l1.getClass()==l2.getClass());
}
結(jié)論:
對于java來說嚎卫,不論泛型的類型具體是什么嘉栓,運行期間兩個List都擁有同一個List對應(yīng)的Class對象。所以無法再一個泛型類,接口中定義靜態(tài)的內(nèi)容去直接使用泛型中的定義的泛型類型侵佃,比如麻昼,不允許下面代碼出現(xiàn):
測試用例:
class Gen<T>{
static T t;//Cannot make a static reference to the non-static type T
}
4-6:不能使用instanceof對于泛型判定
系統(tǒng)不會給每一個泛型的實例對象創(chuàng)建一個泛型對象,也就意味著無法使用instanceOf運算符馋辈。比如:
測試用例:
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
System.out.println(l1.getClass()==l2.getClass());
//Cannot perform instanceof check against parameterized type
//List<String>. Use the form List<?> instead since further
//generic type information will be erased at runtime
System.out.println(l1 instanceof List<String>);
結(jié)論:
我們泛型以上的內(nèi)容抚芦,大概是說是不能用instanceOf去檢查參數(shù)化類型的,也就是我們的泛型迈螟。這樣不允許做燕垃。因為泛型類型的類型信息在運行期就會被抹去。也叫類型擦除井联。
5、泛型邊界問題
5-1: 類型通配符:
測試用例:編寫一個方法用來遍歷當前集合中的元素:
public static void main(String[] args) throws Exception{
List<String> ls = new ArrayList<>();
showAll(ls);
}
public static void showAll(List ls){
for(int i = 0;i<ls.size();i++){
System.out.println(ls.get(i));
}
}
這個方法本質(zhì)上沒有問題您旁,但是注意這個方法在編譯時會出警告烙常,需要指定showAll方法中的參數(shù)化類型,其實就是指定List局部變量的泛型類型鹤盒。改進一版如下:
測試用例1:
public static void main(String[] args) throws Exception{
List<String> ls = new ArrayList<>();
//compile-time error
showAll(ls);
}
public static void showAll(List<Object>ls){
for(int i = 0;i<ls.size();i++){
System.out.println(ls.get(i));
}
}
這個結(jié)論我們在上面已經(jīng)說過了蚕脏,那這個時候如何解決呢?
測試用例2
public static void main(String[] args) throws Exception{
List<String> ls = new ArrayList<>();
//compile-time error
showAll(ls);
}
public static void showAll(List<?>ls){
for(int i = 0;i<ls.size();i++){
System.out.println(ls.get(i));
}
}
結(jié)論:
我們將?
稱之為通配符侦锯,占位符驼鞭。注意最后經(jīng)過類型擦除以后,我們也可以說?是Object尺碰,但是注意挣棕,我們這里其實將List<?>理解為所有List泛型的父類其實更好明白一點。但是這里還是有問題的亲桥,我們在showAll方法做操作:
測試用例3
這個例子特別有意思:
1洛心、雖然我們說通過<?>帶通配符的方式可以傳入任何List對象,不論具體的泛型類型是什么题篷,但是在編譯階段也就導(dǎo)致词身,List<?> ls也無法確定集合中的具體的類型是什么,因為我們查看List中的源碼番枚,public boolean add(E e) {}
法严,這里必須傳入一個E類型的子類或者時候E類型,但是?是無法確定葫笼,所以無法傳入深啤。所以你也無法傳入一個Object對象。
2渔欢、但是我們可以傳入null值墓塌,因為它是所有引用類型的實例。
3、那為什么我們調(diào)用get()方法可以苫幢?我們查看get方法的源碼public E get(int index) {}
访诱;我們發(fā)現(xiàn)這個返回的時一個E類型,未知類型韩肝,那么肯定是一個Obejct触菜,我們輸出會自動調(diào)用該方法的toString,所以沒有問題,我們甚至可以獲取到之后賦值給一個Object類型的變量哀峻,也沒有問題涡相,但是如果要賦值給一個其他類型,要小心了剩蟀,因為不可避免的可能會出現(xiàn)類型轉(zhuǎn)換異常催蝗。
5-2: 泛型的上限問題:
測試用例:
public class Test01 {
public static void main(String[] args) throws Exception{
List<F> lsF = new ArrayList<>();
showAll(lsF);
List<S1> lsS1 = new ArrayList<>();
List<S2> lsS2 = new ArrayList<>();
//compile-time error
showAll(lsS1);
showAll(lsS2);
}
public static void showAll(List<F> ls){
}
}
class F {}
class S1 extends F{}
class S2 extends F{}
結(jié)論:
其實這個結(jié)論上文已經(jīng)提到過了,編譯期間List<S1>和List<F>并不是一會事情育特。一個泛型實例指定了不同的泛型類型丙号,這里不能進行互相轉(zhuǎn)換。如果用?通配符缰冤,似乎能解決問題犬缨。但是不能描述清楚S1、S2和F的關(guān)系棉浸。那這里怎么辦呢怀薛?我們采用泛型的上限
解決該問題。? extends F
迷郑。這里?代表傳入的泛型對象的實際泛型類型枝恋,查看當前類型是否繼承自F,如果繼承就可以直接傳入三热。
public static void showAll(List<? extends F>){}
但是注意:
public static void showAll(List<? extends F> ls){
ls.add(new S1());
}
這個代碼不能這樣寫鼓择。因為傳入的實際類型不確定,導(dǎo)致無法在集合中再添加元素就漾,和我們之前測試的泛型通配符遇到的問題是一致的呐能。有時候我們還會這樣寫:
class Stack <T extends Number & Serializable>{
}
聲明一個Stack類,該類中的泛型聲明為必須是Number的子類抑堡,而且還需要實現(xiàn)Serializable接口摆出。和我們想象的一樣,這里的接口的是可以實現(xiàn)多個的首妖。實現(xiàn)多個用&連接偎漫。
5-3:泛型下限
如果你還記得泛型方法,應(yīng)該知道我們寫了方法有缆,將一個數(shù)組中的元素添加到集合中象踊。我們在上文也提到可以通過下限操作温亲。接下來我們編寫一個實例。完成集合到集合拷貝杯矩,也就意味著src集合中的類型要完全兼容dest集合中的類型栈虚。
public static void main(String[] args) {
Collection<Integer> dest = new ArrayList<>();
Collection<Number> src = new ArrayList<>();
fillIntoColls(dest,src);
}
public static <T> void fillIntoColls(Collection<T> dest ,Collection<? super T> src){
for(T t:dest){
src.add(t);
}
}
這里注意泛型Collection<? super T> src
表示最小是T類型,或者是它的父類史隆。實際傳入的T是Integer類型魂务,而這里泛型下限的值是Number。這里面采用的泛型方法和泛型的下限解決的泌射。
5-4:泛型擦除
測試用例:
public class Test01 {
public static void main(String[] args) {
Gen<Integer> g1;
g1 = new Gen<Integer>(12);
Gen<Integer> g2;
Gen<String> g3 = new Gen<>("test generic");
//Type erasure followed by conversion
Gen gen = g1;
Gen<String> g4 = gen;
}
}
//declared a generic type Gen
class Gen<T>{
T t;
public Gen(T t) {
this.t = t;
}
public void getT(){
System.out.println(t.getClass().getName());
}
}
結(jié)論:最開始定義的g1包含了泛型類型為Integer類型粘姜,然后將g1賦給了一個gen對象,編譯器在這里會丟失掉g1本身的Integer的泛型信息熔酷。這就擦除孤紧,其實我們可以理解為泛型類型由Integer變?yōu)榱薕bject。java允許給一個對象賦給一個具體的泛型類型拒秘。這里只會包檢查警告坛芽,但是如果真的通過g4做一些操作,還是會有可能出現(xiàn)異常的翼抠。
6、jdk8增強的泛型類型推斷
測試代碼:
public class Test01 {
public static void main(String[] args) {
Gen<Integer> g = new Gen<>();//1
Gen<String> g2 = Gen.test01();//2
Gen.test02(123, new Gen<String>());// 3compile-time error
Gen<Integer> g3 = Gen.test02(123, new Gen<>());//4
}
}
//declared a generic type Gen
class Gen<T>{
public static <Z> Gen<Z> test01(){
return null;
}
public static <Z> Gen<Z> test02(Z z,Gen<Z> g){
return null;
}
public T test03(){
return null;
}
}
結(jié)論:
1获讳、第一行代碼創(chuàng)建對象時指定泛型類型阴颖,后續(xù)通過菱形語法直接輸出。這是jdk1.7就支持的
2丐膝、第二行代碼調(diào)用test01方法量愧,沒有指定泛型類型,但是通過調(diào)用方法之后可以帅矗,顯示指定了泛型類型是String類型偎肃,推斷出test01方法的Z泛型類型是String類型
3、編譯報錯浑此,因為調(diào)用方法是傳入的時Z泛型指定的類型是Integer累颂,而傳入的Gen對象的泛型類型變?yōu)榱薙tring類型,所以編譯報錯凛俱。
4紊馏、第四行代碼么問題,因為調(diào)用test02方法時傳入的Z類型指定是Integer類型芳肌,顯式的指定返回的Gen類型也是Integer工碾,可有推斷出傳入的Gen泛型類型是Integer類型没佑,所以直接使用菱形語法。
注意類型推斷不是萬能的赫编。比如下面的代碼
//在上面的main方法加入以下代碼會報錯
String str = Gen.test01().test03();
這里并不能推斷出test01方法返回的時Gen<String>,所以也無法推斷出test03方法返回的時String類型巡蘸。如果要推斷,可以通過以下的方式:
String str = Gen.<String>test01().test03();
7:拓展jdk10增強的局部變量的類型推斷
注意:這里的局部變量的類型推斷和泛型無關(guān)擂送,主要作為擴展知識悦荒。建議使用
Intellij IDEA 2018.1.1以上版本⊥偶祝可以知識jdk10逾冬。或者是JShell
測試實例:這里是基于JShell
jshell> /list
1 : var lists = new ArrayList<String>();
2 : lists.add("hello");
3 : lists.forEach(System.out::println);
結(jié)論:允許通過使用var類型進行類型的聲明躺苦。會自動推斷出當前var的類型是ArrayList:
jshell> System.out.println(lists.getClass());
class java.util.ArrayList
但是注意,var
是一個保留字身腻,而不是關(guān)鍵詞,也以為著你可以通過以下編碼:
jshell> var var = 10;
var ==> 10
但是這里注意匹厘,不能通過var去聲明類嘀趟,接口等∮希可以作為變量名或者是方法名她按。
jshell> interface var{}
| 錯誤:
| 從發(fā)行版 10 開始,
| 此處不允許使用 'var', 'var' 是受限制的本地變量類型, 無法用于類型聲明
| interface var{}
| ^
jshell> class Var{}
| 已創(chuàng)建 類 Var
jshell> /list
1 : var lists = new ArrayList<String>();
2 : lists.add("hello");
3 : lists.forEach(System.out::println);
4 : System.out.println(lists.getClass());
5 : var var = 10;
6 : class Var{}
7 : class Test{void var(){}}
以上算是對于10 的一些嘗鮮吧,其實這只是冰山一角炕柔,我們后續(xù)繼續(xù)填坑酌泰。嘿嘿,挖坑我們是認真的匕累。