? ? ? ?回首望來做了程序員也有些年頭了,一直渾渾噩噩哑子,從事Android也有三年多了,最近回頭一看自己還是什么都不會舅列,覺得自己還是一個小白肌割,可能跟自己的半路出家非計算機專業(yè)出身,以及基礎不扎實導致帐要,所以決定要重新學習以前的知識把敞。重新記錄一下自己學習的心得。做個總結榨惠。
雖然現(xiàn)在有了kotlin 但是也是要編譯成為java去運行奋早,所以還是要提升自己的java語言基礎。java中的泛型算是一個基礎性的東西了赠橙,要想寫一些通用的東西是必不可少的耽装,首先了解一下概念
背景:
Java集合(Collection)中元素的類型是多種多樣的。例如期揪,有些集合中的元素是Byte類型的掉奄,而有些則可能是String類型的,等等凤薛。Java允許程序員構建一個元素類型為Object的Collection姓建,其中的元素可以是任何類型在Java SE?1.5之前,沒有泛型(Generics)的情況下缤苫,通過對類型Object的引用來實現(xiàn)參數(shù)的“任意化”速兔,“任意化”帶來的缺點是要作顯式的強制類型轉換,而這種轉換是要求開發(fā)者對實際參數(shù)類型可以在預知的情況下進行的榨馁。對于強制類型轉換錯誤的情況憨栽,編譯器可能不提示錯誤帜矾,在運行的時候才出現(xiàn)異常翼虫,這是一個安全隱患。因此屡萤,為了解決這一問題珍剑,J2SE 1.5引入泛型也是自然而然的了。
作用:
第一是泛化死陆。可以用T代表任意類型Java語言中引入泛型是一個較大的功能增強不僅語言招拙、類型系統(tǒng)和編譯器有了較大的變化,以支持泛型措译,而且類庫也進行了大翻修别凤,所以許多重要的類,比如集合框架领虹,都已經成為泛型化的了规哪,這帶來了很多好處。
第二是類型安全塌衰。泛型的一個主要目標就是提高ava程序的類型安全诉稍,使用泛型可以使編譯器知道變量的類型限制蝠嘉,進而可以在更高程度上驗證類型假設。如果不用泛型杯巨,則必須使用強制類型轉換蚤告,而強制類型轉換不安全,在運行期可能發(fā)生ClassCast Exception異常服爷,如果使用泛型杜恰,則會在編譯期就能發(fā)現(xiàn)該錯誤。
第三是消除強制類型轉換仍源。泛型可以消除源代碼中的許多強制類型轉換箫章,這樣可以使代碼更加可讀,并減少出錯的機會镜会。
第四是向后兼容檬寂。支持泛型的Java編譯器(例如JDK1.5中的Javac)可以用來編譯經過泛型擴充的Java程序(Generics Java程序),但是現(xiàn)有的沒有使用泛型擴充的Java程序仍然可以用這些編譯器來編譯戳表。
ps:摘抄自百度百科...
學習主要從以下幾個方面來總結一下:
1.我們?yōu)槭裁匆盒?br>2.泛型類桶至,泛型接口,泛型方法
3.如何限定類型變量
4.泛型使用中的約束和局限性
5.泛型類型能繼承嗎匾旭?
6.泛型中通配符類型
7.虛擬機是如何實現(xiàn)泛型的
一:我們?yōu)槭裁匆盒?
1.適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼
舉個栗子:
public int addInt(int a,int b){?
?return a+b;
}public double addDouble(double a,double b){?
return a+b;
}
? ? ? ?這是一段很普通的代碼就是a+b的一個公式計算镣屹,但是當你確定只是需要int值計算的時候是沒有問題的,但是當你需要double,float,long价涝,等的值計算的時候女蜈,顯然就不是那么合適了,需要去重新copy色瘩,然后去一個一個的修改伪窖,改返回值類型,改輸入值的類型居兆。做起來就有點費時費力了覆山,有些人可能會說我時間多,我不嫌煩泥栖,那么你可以去cv去改簇宽,咱這只是一個小的例子,當你方法比較復雜吧享,而且多變呢魏割?當然這個地方只是舉個栗子,真實情況不一定使用泛型就能夠實現(xiàn)需求钢颂。這里只是用這個栗子來說泛型的一種思想钞它。
2.泛型中的類型在使用時指定,不需要強制類型轉換,如果出現(xiàn)數(shù)據(jù)類型錯誤,在編輯期間就可以發(fā)現(xiàn)须揣,不至于在運行期間才可以發(fā)現(xiàn)錯誤盐股。
? List list = new ArrayList();
? list.add("hello");
? list.add("world");
? list.add(5);
? for (int i = 0; i < list.size(); i++) {
? ? ?String name = (String) list.get(i);
????System.out.println("name "+name);
? ?}
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
? ? ? ?很簡單的一段代碼,首先創(chuàng)建一個集合耻卡,然后向集合中先加入兩個字符串疯汁,然后再加入一個數(shù)字,然后去遍歷整個list,可以看到在編譯期是沒有問題卵酪,但是當運行的時候會發(fā)現(xiàn)報錯幌蚊,報一個Integer不能轉換成String的錯誤,list默認類型為Object,因此加入數(shù)據(jù)的時候是沒有問題的溃卡,可是在工作當中溢豆,由于粗心等原因會忘記了之前添加過integer的值所以導致了后期在運行的時候轉換時出錯,而且此問題還不易被發(fā)現(xiàn)瘸羡。
????在上邊的編碼中主要存在兩個問題:
1.當我們將一個對象放入集合中時漩仙,集合不會去記住放入對象的類型,當再次從集合中取出值時,會默認成object類型犹赖,但是它本身實際類型還是其本身類型
2.因此取出集合元素時需要人為的去轉換類型到目標實際類型队他,但是很容易出錯、
因此引出了泛型的泛型中的類型在使用時指定峻村,不需要強制類型轉換的好處麸折。
二:泛型類,泛型接口粘昨,泛型方法
泛型垢啼,即“參數(shù)化類型”。一提到參數(shù)张肾,最熟悉的就是定義方法時有形參芭析,然后調用此方法時傳遞實參。那么參數(shù)化類型怎么理解呢捌浩?
顧名思義放刨,就是將類型由原來的具體的類型參數(shù)化,類似于方法中的變量參數(shù)尸饺,此時類型也定義成參數(shù)形式(可以稱之為類型形參),然后在使用/調用時傳入具體的類型(類型實參)助币。
泛型的本質是為了參數(shù)化類型(在不創(chuàng)建新的類型的情況下浪听,通過泛型指定的不同類型來控制形參具體限制的類型)。也就是說在泛型使用過程中眉菱,操作的數(shù)據(jù)類型被指定為一個參數(shù)迹栓,這種參數(shù)類型可以用在類、接口和方法中俭缓,分別被稱為泛型類克伊、泛型接口酥郭、泛型方法。
引入一個類型變量T(其他大寫字母都可以愿吹,不過常用的就是T不从,E,K犁跪,V等等)椿息,并且用<>括起來,并放在類名的后面坷衍。泛型類是允許有多個類型變量的寝优。
以下為幾個例子
public class Generic<T>{
private T data;
public T getData() {
????return data;
}
public void setData(T data) {
????this.data = data;
}
}public class Generic1<T,K>{
?private T data;
?private K result;
?}
public interface Callback<T>{
?public T result();
}
泛型接口與泛型類的定義基本相同,實現(xiàn)泛型接口的類有兩種實現(xiàn)方法
1.直接在使用的時候指定具體類型 ,實現(xiàn)類new出來的與普通的類沒區(qū)別
public class ImplGenerator implements Callback<String>{
?????@Override
?????public String result(String data) {
? ? ? ? ? ? return "OK"枫耳;
?????}
}
2.未傳入泛型參數(shù)時乏矾,在實現(xiàn)類中實現(xiàn)接口時與實現(xiàn)類的泛型類型保持一致,實現(xiàn)類在new出來時需要指定類型
public class ImplGenerator1<T> implements Callback<T>{
????@Override
????public T result(T data) {
????????return data;
?????}
}//指定類型
ImplGenerator1<String> stringImplGenerator1 = new ImplGenerator1<>();
泛型方法
? ? ?是在調用方法的時候明確指定泛型的具體類型迁杨,泛型方法可以在任何地方和場景使用妻熊,包括普通類和泛型類,注意泛型類型中定義的普通方法和泛型方法的區(qū)別:
1.普通方法
public class Generic<T>{
????private T data;
????public T getData() {???
?????????return data;
????}
????public void setData(T data) {???
?????????this.data = data;
????????}
}
????????此時雖然在getData方法中使用了泛型仑最,但是他并不是泛型方法扔役,這個只是一個普通的成員方法,只不過他是在聲明泛型類的時候指定的泛型類型警医,所以他只是使用了泛型類型T而已亿胸。
2.泛型方法
public <T>T showData(Generic<T> tGeneric){
????T data = tGeneric.getData();
?????return data;
}
????????這才是一個真正的泛型方法,首先泛型方法必須要有<T>表示是一個泛型方法预皇,并且聲明了一個泛型T侈玄,這個T 可以出現(xiàn)在這個泛型方法的任意位置,泛型的數(shù)量也可以是多個吟温。如:public <T,K>T showData(Generic<T> tGeneric){...}
三:如何限定類型變量
有些時候序仙,我們在寫一寫泛型方法的時候不可避免的會對要泛型的類型做出限定,例如如果我們要計算兩個變量的大小
public <T>T min(T a,T b){
?????if (a.compareTo(b) >0 ) return b;else return a;
}
那么如果來保證傳入的泛型類型都帶有compareTo這個方法呢鲁豪?這個時候就要做出限定了潘悼,讓T一定帶有compareTo方法
public <T extends Comparable>T min(T a,T b){
?if (a.compareTo(b) >0 ) return b;else return a;
}
????????T extends Comparable 中 T表示了綁定類型的子類型,Comparable表示了綁定類型爬橡,子類型和綁定類型可以是類也可以是接口治唤,如果這個時候傳入一個沒有實現(xiàn)接口Comparable的類的實例,將會發(fā)生編譯錯誤糙申。
同時extends左右都允許有多個宾添,如 T,V?extends Comparable&Serializable注意限定類型中,只允許有一個類,而且如果有類缕陕,這個類必須是限列表的第一個粱锐。這種類的限定既可以用在泛型方法上也可以用在泛型類上。
四:泛型中的約束和局限性
1.不能用基本類型實例化類型參數(shù)
//這種不被允許
Generic<Int> generic = new Generic<Int>();
2.運行時類型查詢只適用于原始類型
Generic<String> generic1 = new Generic();
Generic generic2 = new Generic();
//以下兩種不被允許
// if(generic1 instanceof Generic<String>)
// if(generic1 instanceof Generic<T>)
System.out.println(generic1.getClass() == generic2.getClass());
System.out.println(generic1.getClass().getName());
//打印結果
true
com.company.Main$Generic
證明了 獲取到的類型只是原始的類型Generic 所以結果為ture
3.泛型類的靜態(tài)上下文中類型變量失效
//靜態(tài)域活方法里不能引用類型變量
private static T instance;
//靜態(tài)方法 本身是泛型方法就行
public static <T>T getInstance(){}
????????不能在靜態(tài)域或方法中引用類型變量扛邑。因為泛型是要在對象創(chuàng)建的時候才知道是什么類型的怜浅,而對象創(chuàng)建的代碼執(zhí)行先后順序是static的部分,然后才是構造函數(shù)等等鹿榜。所以在對象初始化之前static的部分已經執(zhí)行了海雪,如果你在靜態(tài)部分引用的泛型,那么毫無疑問虛擬機根本不知道是什么東西舱殿,因為這個時候類還沒有初始化奥裸。
五:泛型類型的繼承規(guī)則
public class Father {}
public class Son extends Father {}
public class Pair<T> {
????private T one; private T two;
? ? public T getOne() { return one; }
? ? public void setOne(T one) { this.one = one; }
????public T getTwo() { return two; }
????public void setTwo(T two) { this.two = two; }
}//Pair<Father>與Pair<son>沒有任何關系 完全單獨獨立
Pair<Father> fatherPair = new Pair<>();
Pair<Son> son = new Pair<>();
//證明一下 正常情況下 父類可以創(chuàng)建出子類
Father father = new Son();
//會報錯 無法創(chuàng)建顯示類型不一樣
Pair<Father> fatherPair1 = new Pair<Son>();
? 但是泛型類可以繼承或者擴展其他泛型類,比如List和ArrayList
Pair<Father> pair =new ExtendsPair<>();
六:通配符類型
上邊已經說過了Pair<Father>與Pair<son>沒有任何關系 完全單獨獨立沪袭,如果我們有一個泛型類和一個方法呢
public class Fruit {
????private String color;
????public String getColor() { return color; }
? ? public void setColor(String color) { this.color = color; }
}private class Apple extends Fruit {}
private class Orange extends Fruit {}
private class youzi extends Orange {}
public class GenericType<T> {
?private T data;
?public T getData() { return data; }
?public void setData(T data) {
?this.data = data;}
}public void use() {
?GenericType<Fruit> type = new GenericType<>();
?print(type);
?GenericType<Apple> type1 = new GenericType<>();
//這樣是不被容許的
?print(type1);
}
為解決這個問題湾宙,于是提出了一個通配符類型??
有兩種使用方式:
?extends X??表示類型的上界冈绊,類型參數(shù)是X的子類
侠鳄?super X??表示類型的下界,類型參數(shù)是X的超類
這兩種方式從名字上來看死宣,特別是super伟恶,很有迷惑性,下面我們來仔細辨析這兩種方法毅该。
博秫?extends X
表示傳遞給方法的參數(shù),必須是X的子類(包括X本身)
public void print2(
GenericType<? extends Fruit> P) {
?System.out.print(P.getData().getColor());
}
GenericType<youzi> a= new GenericType<>();
print2(a);
但是對泛型類GenericType來說眶掌,如果其中提供了get和set類型參數(shù)變量的方法的話挡育,set方法是不允許被調用的,會出現(xiàn)編譯錯誤
GenericType<Fruit> type1 = new GenericType<>();
Fruit fruit = new Fruit();
Apple apple = new Apple();
//下面的set是不被容許的
type1.setData(fruit);
type1.setData(apple);
get方法則沒問題朴爬,會返回一個Fruit類型的值即寒。
GenericType<Fruit> type = new GenericType<>();
Fruit data1 = type.getData();
為何?
道理很簡單召噩,母赵?extends X??表示類型的上界,類型參數(shù)是X的子類蚣常,那么可以肯定的說市咽,get方法返回的一定是個X(不管是X或者X的子類)編譯器是可以確定知道的。但是set方法只知道傳入的是個X抵蚊,至于具體是X的那個子類,不知道。
總結:主要用于安全地訪問數(shù)據(jù)贞绳,可以訪問X及其子類型谷醉,并且不能寫入非null的數(shù)據(jù)。
冈闭?super X
表示傳遞給方法的參數(shù)俱尼,必須是X的超類(包括X本身)
private void printSuper(GenericType<? super Orange> o) {
????System.out.print(o.getData());
}
private void printUse() {
? ? GenericType<Fruit> type = new GenericType<>();
? ? GenericType<Apple> type1 = new GenericType<>();
????GenericType<Orange> type2 = new GenericType<>();
? ? GenericType<youzi> type3 = new GenericType<>();
? ??printSuper(type);
? ??printSuper(type2);
? ? //下邊兩個會報錯
????printSuper(type1);
? ??printSuper(type3);
}private void printSuper(GenericType<? super Orange> o) {
?System.out.print(o.getData());
}
但是對泛型類GenericType來說,如果其中提供了get和set類型參數(shù)變量的方法的話萎攒,set方法可以被調用的遇八,且能傳入的參數(shù)只能是X或者X的子類
GenericType<? super Orange> genericType = new GenericType<>();?? ?
//下邊的 apple 和 fruit 會報錯???
?genericType.setData(new Apple());????
?genericType.setData(new Fruit());?? ?
//這兩個不會報錯???
?genericType.setData(new Orange());???
?genericType.setData(new youzi());?//一定是OBJECT
Object data = genericType.getData();
get方法只會返回一個Object類型的值
為何?
耍休?super ?X??表示類型的下界刃永,類型參數(shù)是X的超類(包括X本身),那么可以肯定的說羊精,get方法返回的一定是個X的超類斯够,那么到底是哪個超類?不知道喧锦,但是可以肯定的說读规,Object一定是它的超類,所以get方法返回Object燃少。編譯器是可以確定知道的束亏。對于set方法來說,編譯器不知道它需要的確切類型阵具,但是X和X的子類可以安全的轉型為X碍遍。
總結:主要用于安全地寫入數(shù)據(jù),可以寫入X及其子類型怔昨。
虛擬機是如何實現(xiàn)泛型的雀久?
????????泛型思想早在C++語言的模板(Template)中就開始生根發(fā)芽,在Java語言處于還沒有出現(xiàn)泛型的版本時趁舀,只能通過Object是所有類型的父類和類型強制轉換兩個特點的配合來實現(xiàn)類型泛化赖捌。,由于Java語言里面所有的類型都繼承于java.lang.Object矮烹,所以Object轉型成任何對象都是有可能的越庇。但是也因為有無限的可能性,就只有程序員和運行期的虛擬機才知道這個Object到底是個什么類型的對象奉狈。在編譯期間卤唉,編譯器無法檢查這個Object的強制轉型是否成功,如果僅僅依賴程序員去保障這項操作的正確性仁期,許多ClassCastException的風險就會轉嫁到程序運行期之中图焰。
????????泛型技術在C#和Java之中的使用方式看似相同,但實現(xiàn)上卻有著根本性的分歧皮假,C#里面泛型無論在程序源碼中藻雌、編譯后的IL中(Intermediate Language,中間語言,這時候泛型是一個占位符),或是運行期的CLR中,都是切實存在的岔绸,List<integer>與List<String>就是兩個不同的類型,它們在系統(tǒng)運行期生成橡伞,有自己的虛方法表和類型數(shù)據(jù)盒揉,這種實現(xiàn)稱為類型膨脹,基于這種方法實現(xiàn)的泛型稱為真實泛型兑徘。
????????Java語言中的泛型則不一樣刚盈,它只在程序源碼中存在,在編譯后的字節(jié)碼文件中道媚,就已經替換為原來的原生類型(Raw Type扁掸,也稱為裸類型)了,并且在相應的地方插入了強制轉型代碼最域,因此谴分,對于運行期的Java語言來說,ArrayList<integer>與ArrayList<String>就是同一個類镀脂,所以泛型技術實際上是Java語言的一顆語法糖牺蹄,Java語言中的泛型實現(xiàn)方法稱為類型擦除,基于這種方法實現(xiàn)的泛型稱為偽泛型薄翅。
????????將一段Java代碼編譯成Class文件沙兰,然后再用字節(jié)碼反編譯工具進行反編譯后,將會發(fā)現(xiàn)泛型都不見了翘魄,程序又變回了Java泛型出現(xiàn)之前的寫法鼎天,泛型類型都變回了原生類型
????????上面這段代碼是不能被編譯的,因為參數(shù)List<Integer>和List<String>編譯之后都被擦除了暑竟,變成了一樣的原生類型List<E>斋射,擦除動作導致這兩種方法的特征簽名變得一模一樣。
????????由于Java泛型的引入但荤,各種場景(虛擬機解析罗岖、反射等)下的方法調用都可能對原有的基礎產生影響和新的需求,如在泛型類中如何獲取傳入的參數(shù)化類型等腹躁。因此桑包,JCP組織對虛擬機規(guī)范做出了相應的修改,引入了諸如Signature纺非、LocalVariableTypeTable等新的屬性用于解決伴隨泛型而來的參數(shù)類型的識別問題哑了,Signature是其中最重要的一項屬性赘方,它的作用就是存儲一個方法在字節(jié)碼層面的特征簽名[3],這個屬性中保存的參數(shù)類型并不是原生類型垒手,而是包括了參數(shù)化類型的信息蒜焊。修改后的虛擬機規(guī)范要求所有能識別49.0以上版本的Class文件的虛擬機都要能正確地識別Signature參數(shù)倒信。
????????另外科贬,從Signature屬性的出現(xiàn)我們還可以得出結論,擦除法所謂的擦除鳖悠,僅僅是對方法的Code屬性中的字節(jié)碼進行擦除榜掌,實際上元數(shù)據(jù)中還是保留了泛型信息,這也是我們能通過反射手段取得參數(shù)化類型的根本依據(jù)乘综。