java中泛型的理解

? ? ? ?回首望來做了程序員也有些年頭了,一直渾渾噩噩,從事Android也有三年多了,最近回頭一看自己還是什么都不會(huì),覺得自己還是一個(gè)小白,可能跟自己的半路出家非計(jì)算機(jī)專業(yè)出身,以及基礎(chǔ)不扎實(shí)導(dǎo)致,所以決定要重新學(xué)習(xí)以前的知識(shí)。重新記錄一下自己學(xué)習(xí)的心得。做個(gè)總結(jié)。


雖然現(xiàn)在有了kotlin 但是也是要編譯成為java去運(yùn)行,所以還是要提升自己的java語言基礎(chǔ)。java中的泛型算是一個(gè)基礎(chǔ)性的東西了,要想寫一些通用的東西是必不可少的,首先了解一下概念

背景:

Java集合(Collection)中元素的類型是多種多樣的。例如,有些集合中的元素是Byte類型的,而有些則可能是String類型的,等等。Java允許程序員構(gòu)建一個(gè)元素類型為Object的Collection,其中的元素可以是任何類型在Java SE?1.5之前,沒有泛型(Generics)的情況下,通過對(duì)類型Object的引用來實(shí)現(xiàn)參數(shù)的“任意化”,“任意化”帶來的缺點(diǎn)是要作顯式的強(qiáng)制類型轉(zhuǎn)換,而這種轉(zhuǎn)換是要求開發(fā)者對(duì)實(shí)際參數(shù)類型可以在預(yù)知的情況下進(jìn)行的。對(duì)于強(qiáng)制類型轉(zhuǎn)換錯(cuò)誤的情況,編譯器可能不提示錯(cuò)誤,在運(yùn)行的時(shí)候才出現(xiàn)異常,這是一個(gè)安全隱患。因此,為了解決這一問題,J2SE 1.5引入泛型也是自然而然的了。

作用:

第一是泛化。可以用T代表任意類型Java語言中引入泛型是一個(gè)較大的功能增強(qiáng)不僅語言、類型系統(tǒng)和編譯器有了較大的變化,以支持泛型,而且類庫也進(jìn)行了大翻修,所以許多重要的類,比如集合框架,都已經(jīng)成為泛型化的了,這帶來了很多好處。

第二是類型安全。泛型的一個(gè)主要目標(biāo)就是提高ava程序的類型安全,使用泛型可以使編譯器知道變量的類型限制,進(jìn)而可以在更高程度上驗(yàn)證類型假設(shè)。如果不用泛型,則必須使用強(qiáng)制類型轉(zhuǎn)換,而強(qiáng)制類型轉(zhuǎn)換不安全,在運(yùn)行期可能發(fā)生ClassCast Exception異常,如果使用泛型,則會(huì)在編譯期就能發(fā)現(xiàn)該錯(cuò)誤。

第三是消除強(qiáng)制類型轉(zhuǎn)換。泛型可以消除源代碼中的許多強(qiáng)制類型轉(zhuǎn)換,這樣可以使代碼更加可讀,并減少出錯(cuò)的機(jī)會(huì)。

第四是向后兼容。支持泛型的Java編譯器(例如JDK1.5中的Javac)可以用來編譯經(jīng)過泛型擴(kuò)充的Java程序(Generics Java程序),但是現(xiàn)有的沒有使用泛型擴(kuò)充的Java程序仍然可以用這些編譯器來編譯。

ps:摘抄自百度百科...


學(xué)習(xí)主要從以下幾個(gè)方面來總結(jié)一下:
1.我們?yōu)槭裁匆盒?br>2.泛型類,泛型接口,泛型方法
3.如何限定類型變量
4.泛型使用中的約束和局限性
5.泛型類型能繼承嗎?
6.泛型中通配符類型
7.虛擬機(jī)是如何實(shí)現(xiàn)泛型的

一:我們?yōu)槭裁匆盒?

1.適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼

舉個(gè)栗子:

public int addInt(int a,int b){?
?return a+b;
}

public double addDouble(double a,double b){?
return a+b;
}

? ? ? ?這是一段很普通的代碼就是a+b的一個(gè)公式計(jì)算,但是當(dāng)你確定只是需要int值計(jì)算的時(shí)候是沒有問題的,但是當(dāng)你需要double,float,long,等的值計(jì)算的時(shí)候,顯然就不是那么合適了,需要去重新copy,然后去一個(gè)一個(gè)的修改,改返回值類型,改輸入值的類型。做起來就有點(diǎn)費(fèi)時(shí)費(fèi)力了,有些人可能會(huì)說我時(shí)間多,我不嫌煩,那么你可以去cv去改,咱這只是一個(gè)小的例子,當(dāng)你方法比較復(fù)雜,而且多變呢?當(dāng)然這個(gè)地方只是舉個(gè)栗子,真實(shí)情況不一定使用泛型就能夠?qū)崿F(xiàn)需求。這里只是用這個(gè)栗子來說泛型的一種思想

2.泛型中的類型在使用時(shí)指定,不需要強(qiáng)制類型轉(zhuǎn)換,如果出現(xiàn)數(shù)據(jù)類型錯(cuò)誤,在編輯期間就可以發(fā)現(xiàn),不至于在運(yùn)行期間才可以發(fā)現(xiàn)錯(cuò)誤。

? 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)建一個(gè)集合,然后向集合中先加入兩個(gè)字符串,然后再加入一個(gè)數(shù)字,然后去遍歷整個(gè)list,可以看到在編譯期是沒有問題,但是當(dāng)運(yùn)行的時(shí)候會(huì)發(fā)現(xiàn)報(bào)錯(cuò),報(bào)一個(gè)Integer不能轉(zhuǎn)換成String的錯(cuò)誤,list默認(rèn)類型為Object,因此加入數(shù)據(jù)的時(shí)候是沒有問題的,可是在工作當(dāng)中,由于粗心等原因會(huì)忘記了之前添加過integer的值所以導(dǎo)致了后期在運(yùn)行的時(shí)候轉(zhuǎn)換時(shí)出錯(cuò),而且此問題還不易被發(fā)現(xiàn)。
????在上邊的編碼中主要存在兩個(gè)問題:
1.當(dāng)我們將一個(gè)對(duì)象放入集合中時(shí),集合不會(huì)去記住放入對(duì)象的類型,當(dāng)再次從集合中取出值時(shí),會(huì)默認(rèn)成object類型,但是它本身實(shí)際類型還是其本身類型
2.因此取出集合元素時(shí)需要人為的去轉(zhuǎn)換類型到目標(biāo)實(shí)際類型,但是很容易出錯(cuò)、

因此引出了泛型的泛型中的類型在使用時(shí)指定,不需要強(qiáng)制類型轉(zhuǎn)換的好處。

二:泛型類,泛型接口,泛型方法

泛型,即“參數(shù)化類型”。一提到參數(shù),最熟悉的就是定義方法時(shí)有形參,然后調(diào)用此方法時(shí)傳遞實(shí)參。那么參數(shù)化類型怎么理解呢?

顧名思義,就是將類型由原來的具體的類型參數(shù)化,類似于方法中的變量參數(shù),此時(shí)類型也定義成參數(shù)形式(可以稱之為類型形參),然后在使用/調(diào)用時(shí)傳入具體的類型(類型實(shí)參)。

泛型的本質(zhì)是為了參數(shù)化類型(在不創(chuàng)建新的類型的情況下,通過泛型指定的不同類型來控制形參具體限制的類型)。也就是說在泛型使用過程中,操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù),這種參數(shù)類型可以用在類、接口和方法中,分別被稱為泛型類、泛型接口、泛型方法。

引入一個(gè)類型變量T(其他大寫字母都可以,不過常用的就是T,E,K,V等等),并且用<>括起來,并放在類名的后面。泛型類是允許有多個(gè)類型變量的。

以下為幾個(gè)例子

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();
}

泛型接口與泛型類的定義基本相同,實(shí)現(xiàn)泛型接口的類有兩種實(shí)現(xiàn)方法
1.直接在使用的時(shí)候指定具體類型 ,實(shí)現(xiàn)類new出來的與普通的類沒區(qū)別

public class ImplGenerator implements Callback<String>{
?????@Override
?????public String result(String data) {
? ? ? ? ? ? return "OK";
?????}
}

2.未傳入泛型參數(shù)時(shí),在實(shí)現(xiàn)類中實(shí)現(xiàn)接口時(shí)與實(shí)現(xiàn)類的泛型類型保持一致,實(shí)現(xiàn)類在new出來時(shí)需要指定類型

public class ImplGenerator1<T> implements Callback<T>{
????@Override
????public T result(T data) {
????????return data;
?????}
}

//指定類型
ImplGenerator1<String> stringImplGenerator1 = new ImplGenerator1<>();


泛型方法
? ? ?是在調(diào)用方法的時(shí)候明確指定泛型的具體類型,泛型方法可以在任何地方和場(chǎng)景使用,包括普通類和泛型類,注意泛型類型中定義的普通方法和泛型方法的區(qū)別:

1.普通方法

public class Generic<T>{
????private T data;
????public T getData() {???
?????????return data;
????}
????public void setData(T data) {???
?????????this.data = data;
????????}
}

????????此時(shí)雖然在getData方法中使用了泛型,但是他并不是泛型方法,這個(gè)只是一個(gè)普通的成員方法,只不過他是在聲明泛型類的時(shí)候指定的泛型類型,所以他只是使用了泛型類型T而已。

2.泛型方法

public <T>T showData(Generic<T> tGeneric){
????T data = tGeneric.getData();
?????return data;
}

????????這才是一個(gè)真正的泛型方法,首先泛型方法必須要有<T>表示是一個(gè)泛型方法,并且聲明了一個(gè)泛型T,這個(gè)T 可以出現(xiàn)在這個(gè)泛型方法的任意位置,泛型的數(shù)量也可以是多個(gè)。如:public <T,K>T showData(Generic<T> tGeneric){...}

三:如何限定類型變量
有些時(shí)候,我們?cè)趯懸粚懛盒头椒ǖ臅r(shí)候不可避免的會(huì)對(duì)要泛型的類型做出限定,例如如果我們要計(jì)算兩個(gè)變量的大小

public <T>T min(T a,T b){
?????if (a.compareTo(b) >0 ) return b;else return a;
}

那么如果來保證傳入的泛型類型都帶有compareTo這個(gè)方法呢?這個(gè)時(shí)候就要做出限定了,讓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表示了綁定類型,子類型和綁定類型可以是類也可以是接口,如果這個(gè)時(shí)候傳入一個(gè)沒有實(shí)現(xiàn)接口Comparable的類的實(shí)例,將會(huì)發(fā)生編譯錯(cuò)誤。

同時(shí)extends左右都允許有多個(gè),如 T,V?extends Comparable&Serializable注意限定類型中,只允許有一個(gè)類,而且如果有類,這個(gè)類必須是限列表的第一個(gè)。這種類的限定既可以用在泛型方法上也可以用在泛型類上。

四:泛型中的約束和局限性

1.不能用基本類型實(shí)例化類型參數(shù)

//這種不被允許
Generic<Int> generic = new Generic<Int>();

2.運(yùn)行時(shí)類型查詢只適用于原始類型

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());
//打印結(jié)果
true
com.company.Main$Generic

證明了 獲取到的類型只是原始的類型Generic 所以結(jié)果為ture

3.泛型類的靜態(tài)上下文中類型變量失效

//靜態(tài)域活方法里不能引用類型變量
private static T instance;
//靜態(tài)方法 本身是泛型方法就行
public static <T>T getInstance(){}

????????不能在靜態(tài)域或方法中引用類型變量。因?yàn)榉盒褪且趯?duì)象創(chuàng)建的時(shí)候才知道是什么類型的,而對(duì)象創(chuàng)建的代碼執(zhí)行先后順序是static的部分,然后才是構(gòu)造函數(shù)等等。所以在對(duì)象初始化之前static的部分已經(jīng)執(zhí)行了,如果你在靜態(tài)部分引用的泛型,那么毫無疑問虛擬機(jī)根本不知道是什么東西,因?yàn)檫@個(gè)時(shí)候類還沒有初始化。

五:泛型類型的繼承規(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>沒有任何關(guān)系 完全單獨(dú)獨(dú)立
Pair<Father> fatherPair = new Pair<>();
Pair<Son> son = new Pair<>();
//證明一下 正常情況下 父類可以創(chuàng)建出子類
Father father = new Son();
//會(huì)報(bào)錯(cuò) 無法創(chuàng)建顯示類型不一樣
Pair<Father> fatherPair1 = new Pair<Son>();

? 但是泛型類可以繼承或者擴(kuò)展其他泛型類,比如List和ArrayList

Pair<Father> pair =new ExtendsPair<>();

六:通配符類型

上邊已經(jīng)說過了Pair<Father>與Pair<son>沒有任何關(guān)系 完全單獨(dú)獨(dú)立,如果我們有一個(gè)泛型類和一個(gè)方法呢

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);
}

為解決這個(gè)問題,于是提出了一個(gè)通配符類型??

有兩種使用方式:

?extends X??表示類型的上界,類型參數(shù)是X的子類

?super X??表示類型的下界,類型參數(shù)是X的超類

這兩種方式從名字上來看,特別是super,很有迷惑性,下面我們來仔細(xì)辨析這兩種方法。

?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);

但是對(duì)泛型類GenericType來說,如果其中提供了get和set類型參數(shù)變量的方法的話,set方法是不允許被調(diào)用的,會(huì)出現(xiàn)編譯錯(cuò)誤

GenericType<Fruit> type1 = new GenericType<>();
Fruit fruit = new Fruit();
Apple apple = new Apple();
//下面的set是不被容許的
type1.setData(fruit);
type1.setData(apple);

get方法則沒問題,會(huì)返回一個(gè)Fruit類型的值。

GenericType<Fruit> type = new GenericType<>();
Fruit data1 = type.getData();

為何?

道理很簡單,?extends X??表示類型的上界,類型參數(shù)是X的子類,那么可以肯定的說,get方法返回的一定是個(gè)X(不管是X或者X的子類)編譯器是可以確定知道的。但是set方法只知道傳入的是個(gè)X,至于具體是X的那個(gè)子類,不知道。

總結(jié):主要用于安全地訪問數(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);
? ? //下邊兩個(gè)會(huì)報(bào)錯(cuò)
????printSuper(type1);
? ??printSuper(type3);
}

private void printSuper(GenericType<? super Orange> o) {
?System.out.print(o.getData());
}

但是對(duì)泛型類GenericType來說,如果其中提供了get和set類型參數(shù)變量的方法的話,set方法可以被調(diào)用的,且能傳入的參數(shù)只能是X或者X的子類

GenericType<? super Orange> genericType = new GenericType<>();?? ?
//下邊的 apple 和 fruit 會(huì)報(bào)錯(cuò)???
?genericType.setData(new Apple());????
?genericType.setData(new Fruit());?? ?
//這兩個(gè)不會(huì)報(bào)錯(cuò)???
?genericType.setData(new Orange());???
?genericType.setData(new youzi());?

//一定是OBJECT
Object data = genericType.getData();

get方法只會(huì)返回一個(gè)Object類型的值

為何?

?super ?X??表示類型的下界,類型參數(shù)是X的超類(包括X本身),那么可以肯定的說,get方法返回的一定是個(gè)X的超類,那么到底是哪個(gè)超類?不知道,但是可以肯定的說,Object一定是它的超類,所以get方法返回Object。編譯器是可以確定知道的。對(duì)于set方法來說,編譯器不知道它需要的確切類型,但是X和X的子類可以安全的轉(zhuǎn)型為X。

總結(jié):主要用于安全地寫入數(shù)據(jù),可以寫入X及其子類型。

虛擬機(jī)是如何實(shí)現(xiàn)泛型的?

????????泛型思想早在C++語言的模板(Template)中就開始生根發(fā)芽,在Java語言處于還沒有出現(xiàn)泛型的版本時(shí),只能通過Object是所有類型的父類和類型強(qiáng)制轉(zhuǎn)換兩個(gè)特點(diǎn)的配合來實(shí)現(xiàn)類型泛化。,由于Java語言里面所有的類型都繼承于java.lang.Object,所以O(shè)bject轉(zhuǎn)型成任何對(duì)象都是有可能的。但是也因?yàn)橛袩o限的可能性,就只有程序員和運(yùn)行期的虛擬機(jī)才知道這個(gè)Object到底是個(gè)什么類型的對(duì)象。在編譯期間,編譯器無法檢查這個(gè)Object的強(qiáng)制轉(zhuǎn)型是否成功,如果僅僅依賴程序員去保障這項(xiàng)操作的正確性,許多ClassCastException的風(fēng)險(xiǎn)就會(huì)轉(zhuǎn)嫁到程序運(yùn)行期之中。
????????泛型技術(shù)在C#和Java之中的使用方式看似相同,但實(shí)現(xiàn)上卻有著根本性的分歧,C#里面泛型無論在程序源碼中、編譯后的IL中(Intermediate Language,中間語言,這時(shí)候泛型是一個(gè)占位符),或是運(yùn)行期的CLR中,都是切實(shí)存在的,List<integer>與List<String>就是兩個(gè)不同的類型,它們?cè)谙到y(tǒng)運(yùn)行期生成,有自己的虛方法表和類型數(shù)據(jù),這種實(shí)現(xiàn)稱為類型膨脹,基于這種方法實(shí)現(xiàn)的泛型稱為真實(shí)泛型。
????????Java語言中的泛型則不一樣,它只在程序源碼中存在,在編譯后的字節(jié)碼文件中,就已經(jīng)替換為原來的原生類型(Raw Type,也稱為裸類型)了,并且在相應(yīng)的地方插入了強(qiáng)制轉(zhuǎn)型代碼,因此,對(duì)于運(yùn)行期的Java語言來說,ArrayList<integer>與ArrayList<String>就是同一個(gè)類,所以泛型技術(shù)實(shí)際上是Java語言的一顆語法糖,Java語言中的泛型實(shí)現(xiàn)方法稱為類型擦除,基于這種方法實(shí)現(xiàn)的泛型稱為偽泛型。
????????將一段Java代碼編譯成Class文件,然后再用字節(jié)碼反編譯工具進(jìn)行反編譯后,將會(huì)發(fā)現(xiàn)泛型都不見了,程序又變回了Java泛型出現(xiàn)之前的寫法,泛型類型都變回了原生類型

method

????????上面這段代碼是不能被編譯的,因?yàn)閰?shù)List<Integer>和List<String>編譯之后都被擦除了,變成了一樣的原生類型List<E>,擦除動(dòng)作導(dǎo)致這兩種方法的特征簽名變得一模一樣。
????????由于Java泛型的引入,各種場(chǎng)景(虛擬機(jī)解析、反射等)下的方法調(diào)用都可能對(duì)原有的基礎(chǔ)產(chǎn)生影響和新的需求,如在泛型類中如何獲取傳入的參數(shù)化類型等。因此,JCP組織對(duì)虛擬機(jī)規(guī)范做出了相應(yīng)的修改,引入了諸如Signature、LocalVariableTypeTable等新的屬性用于解決伴隨泛型而來的參數(shù)類型的識(shí)別問題,Signature是其中最重要的一項(xiàng)屬性,它的作用就是存儲(chǔ)一個(gè)方法在字節(jié)碼層面的特征簽名[3],這個(gè)屬性中保存的參數(shù)類型并不是原生類型,而是包括了參數(shù)化類型的信息。修改后的虛擬機(jī)規(guī)范要求所有能識(shí)別49.0以上版本的Class文件的虛擬機(jī)都要能正確地識(shí)別Signature參數(shù)。
????????另外,從Signature屬性的出現(xiàn)我們還可以得出結(jié)論,擦除法所謂的擦除,僅僅是對(duì)方法的Code屬性中的字節(jié)碼進(jìn)行擦除,實(shí)際上元數(shù)據(jù)中還是保留了泛型信息,這也是我們能通過反射手段取得參數(shù)化類型的根本依據(jù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 泛型(Generics)的型變是Java中比較難以理解和使用的部分,“神秘”的通配符,讓我看了幾遍《Java...
    珞澤珈群閱讀 8,127評(píng)論 12 51
  • 1.泛型簡介 問題:在獲取用戶信息的API中,后臺(tái)給我們返回一個(gè)這樣形式的json字符串。{ "meta":...
    彼岸之城cyy閱讀 1,064評(píng)論 0 0
  • 泛型程序設(shè)計(jì) 泛型程序設(shè)計(jì)意味著編寫的代碼可以被很多不同類型的對(duì)象所重用。例如ArrayList類可以聚集任何類型...
    Steven1997閱讀 770評(píng)論 1 0
  • 引言:泛型一直是困擾自己的一個(gè)難題,但是泛型有時(shí)一個(gè)面試時(shí)老生常談的問題;今天作者就通過查閱相關(guān)資料簡單談?wù)勛约簩?duì)...
    cp_insist閱讀 1,937評(píng)論 0 4
  • 做一個(gè)酷酷的人,做一件酷酷的事。堅(jiān)持寫日記,日積月累就能達(dá)到一個(gè)巔峰狀態(tài)。故事營銷都是從有積累開始,當(dāng)寫日記長時(shí)間...
    劉帥飛閱讀 290評(píng)論 0 0

友情鏈接更多精彩內(nèi)容