Java中的泛型

Java語(yǔ)言高級(jí)特性前言:

java知識(shí)是作為Android開(kāi)發(fā)的語(yǔ)言基礎(chǔ),雖然現(xiàn)在我們已經(jīng)推出了kotlin,但是基于以下原因我們還是需要好好牢牢掌握java:
1)SDK還是改成java,kotlin也需要編譯成為java運(yùn)行;
2)目前大量的第三方庫(kù)和繼承與前任的代碼都是java所寫(xiě)的;
3)Java語(yǔ)言應(yīng)用不僅僅在Android,就是在后臺(tái)開(kāi)發(fā)中也是一個(gè)最流行的語(yǔ)言;

1、為什么我們需要泛型?
eg1:
public class Add {
    public int add(int a,int b){

        return a+b;
    }

    public float add(float a,float b){

        return a+b;
    }

    public double add(double a,double b){

        return a+b;
    }

    public float add(int a,float b){

        return a+b;
    }

    //TODO ... 等等數(shù)據(jù)類(lèi)型組合
}

實(shí)際開(kāi)發(fā)中,經(jīng)常有數(shù)值類(lèi)型求和的需求,例如實(shí)現(xiàn)int類(lèi)型的加法, 有時(shí)候還需要實(shí)現(xiàn)long類(lèi)型的求和, 如果還需要double類(lèi)型的求和,需要重新在重載一個(gè)輸入是double類(lèi)型的add方法。 雖然可以進(jìn)行方法重載可以解決,但是你不覺(jué)得麻煩嘛?

eg2:
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {

        List list = new ArrayList();

        list.add("123");
        list.add(123);

        for (int i = 0; i < list.size(); i++) {
            String element = (String) list.get(i);
            System.out.println(element);
        }
    }
}
輸出結(jié)果:
123

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

    at com.jzsk.javaadvancetest.ExampleUnitTest.addition_isCorrect(ExampleUnitTest.java:25)

定義了一個(gè)List類(lèi)型的集合,先向其中加入了一個(gè)字符串類(lèi)型的值,隨后加入一個(gè)Integer類(lèi)型的值。這是完全允許的,因?yàn)榇藭r(shí)list默認(rèn)的類(lèi)型為Object類(lèi)型。在之后的循環(huán)中,由于忘記了之前在list中也加入了Integer類(lèi)型的值或其他編碼原因,很容易出現(xiàn)類(lèi)似于上述中的錯(cuò)誤。因?yàn)榫幾g階段正常,而運(yùn)行時(shí)會(huì)出現(xiàn)“java.lang.ClassCastException”異常。因此,導(dǎo)致此類(lèi)錯(cuò)誤編碼過(guò)程中不易發(fā)現(xiàn)。
在如上的編碼過(guò)程中,我們發(fā)現(xiàn)主要存在兩個(gè)問(wèn)題:
1.當(dāng)我們將一個(gè)對(duì)象放入集合中,集合不會(huì)記住此對(duì)象的類(lèi)型,當(dāng)再次從集合中取出此對(duì)象時(shí),改對(duì)象的編譯類(lèi)型變成了Object類(lèi)型,但其運(yùn)行時(shí)類(lèi)型任然為其本身類(lèi)型。
2.因此,取出集合元素時(shí)需要人為的強(qiáng)制類(lèi)型轉(zhuǎn)化到具體的目標(biāo)類(lèi)型,且很容易出現(xiàn)“java.lang.ClassCastException”異常。
所以泛型的好處就是:

適用于多種數(shù)據(jù)類(lèi)型執(zhí)行相同的代碼
泛型中的類(lèi)型在使用時(shí)指定,不需要強(qiáng)制類(lèi)型轉(zhuǎn)換

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

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

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

2、泛型類(lèi)、泛型接口、泛型方法
泛型類(lèi)

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

public class GenericClass<T> {
    private T data;
    public GenericClass(T data) {
        this.data = data;
    }
    
}
public class GenericClass<T,V> {
    private T data;
    private V value;
    public GenericClass(T data, V value) {
        this.data = data;
        this.value = value;
    }
}
泛型接口
public interface GenericInterface<T> {
    
    T getData();

}
泛型接口的實(shí)現(xiàn):

1、未傳入泛型實(shí)參時(shí):

public class GenericInterfaceImpl<T> implements GenericInterface<T> {
    @Override
    public T getData() {
        return null;
    }
}

2、傳入泛型實(shí)參

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        
        GenericInterface<String> genericInterface = new GenericInterface<String>() {
            @Override
            public String getData() {
                return null;
            }
        };
        
    }
}
泛型方法:注意有<T>才是泛型方法
public class GenericMethod {
    public <T> T getValue(T t){
        return t;
    }
}

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

普通方法:

public GenericClass(T data, T value) {
       this.data = data;
       this.value = value;
   }

泛型方法:

public <T> T getValue(T t){
        return t;
    }

public <T> void setValue(T t){
     //TODO do something
    }

限定類(lèi)型變量
public static <T> T min (T a,T b){
       return a.compareTo(b)>0?a:b;
   }

這樣寫(xiě)存在問(wèn)題:有時(shí)候,我們需要對(duì)類(lèi)型變量加以約束,比如計(jì)算兩個(gè)變量的最小,最大值。請(qǐng)問(wèn),如果確保傳入的兩個(gè)變量一定有compareTo方法?那么解決這個(gè)問(wèn)題的方案就是將T限制為實(shí)現(xiàn)了接口Comparable的類(lèi)

public static <T extends Comparable> T min (T a,T b){
       return a.compareTo(b)>0?a:b;
   }

T extends Comparable中
T表示應(yīng)該綁定類(lèi)型的子類(lèi)型,Comparable表示綁定類(lèi)型,子類(lèi)型和綁定類(lèi)型可以是類(lèi)也可以是接口。
如果這個(gè)時(shí)候,我們?cè)噲D傳入一個(gè)沒(méi)有實(shí)現(xiàn)接口Comparable的類(lèi)的實(shí)例,將會(huì)發(fā)生編譯錯(cuò)誤。


image.png

因?yàn)锳dd對(duì)象并沒(méi)有實(shí)現(xiàn)Comparable接口,不滿(mǎn)足條件,所以報(bào)錯(cuò)。

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

3、泛型中的約束和局限性

1、不能用基本類(lèi)型實(shí)例化類(lèi)型參數(shù)

//error:type argument can not be of primitive type
GenericClass<int,int> genericClassint = new GenericClass<>(1,2);
//ok
GenericClass<Integer,Integer> genericClassInt = new GenericClass<>(1,2);

2、運(yùn)行時(shí)類(lèi)型查詢(xún)只適用于原始類(lèi)型

//error:lllegal generic type for instanceof
if (genericClassInt instanceof GenericClass<Integer,Integer>){

}

//error:Cannot resolve symbol 'T'
if (genericClassInt instanceof GenericClass<T,T>){

}

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

4、不能創(chuàng)建參數(shù)化類(lèi)型的數(shù)組

//ok
GenericClass<Integer> [] genericClasses;
//error
GenericClass<Integer> [] genericClasses1 = new GenericClass<Integer>[10];
4、泛型類(lèi)型的繼承規(guī)則
//父類(lèi)
public class Person {
    //TODO 
}
// 子類(lèi)
public class Man extends Person { {
    //TODO 
}
public class Worker<T> {
    private T t;
    public Worker(T t) {
        this.t = t;
    }
}

請(qǐng)問(wèn)Worker <Person>和Worker <Man>是繼承關(guān)系嗎?
答案:不是,他們之間沒(méi)有什么關(guān)系

Person person = new Man();
//error:incompatible types.
Man man = new Person();
//error:expression expected
Worker<Person> worker = new Worker<>(Man);
5、通配符類(lèi)型

? extends X 表示類(lèi)型的上界,類(lèi)型參數(shù)是X的子類(lèi)
? super X 表示類(lèi)型的下界,類(lèi)型參數(shù)是X的超類(lèi)
這兩種 方式從名字上來(lái)看,特別是super,很有迷惑性,下面我們來(lái)仔細(xì)辨析這兩種方法:
1、? extends X
表示傳遞給方法的參數(shù),必須是X的子類(lèi)(包括X本身)
但是對(duì)泛型類(lèi)GenericClass來(lái)說(shuō),如果其中提供了get和set類(lèi)型參數(shù)變量的方法的話(huà),set方法是不允許被調(diào)用的,會(huì)出現(xiàn)編譯錯(cuò)誤。
get方法則沒(méi)問(wèn)題,會(huì)返回一個(gè)父類(lèi)類(lèi)型的值。
為何?
道理很簡(jiǎn)單,? extends X 表示類(lèi)型的上界,類(lèi)型參數(shù)是X的子類(lèi),那么可以肯定的說(shuō),get方法返回的一定是個(gè)X(不管是X或者X的子類(lèi))編譯器是可以確定知道的。但是set方法只知道傳入的是個(gè)X,至于具體是X的那個(gè)子類(lèi),不知道。
總結(jié):主要用于安全地訪問(wèn)數(shù)據(jù),可以訪問(wèn)X及其子類(lèi)型,并且不能寫(xiě)入非null的數(shù)據(jù)。
2、? super X
表示傳遞給方法的參數(shù),必須是X的超類(lèi)(包括X本身)
但是對(duì)泛型類(lèi)GenericType來(lái)說(shuō),如果其中提供了get和set類(lèi)型參數(shù)變量的方法的話(huà),set方法可以被調(diào)用的,且能傳入的參數(shù)只能是X或者X的超類(lèi)
get方法只會(huì)返回一個(gè)Object類(lèi)型的值。
為何?
? super X 表示類(lèi)型的下界,類(lèi)型參數(shù)是X的超類(lèi)(包括X本身),那么可以肯定的說(shuō),get方法返回的一定是個(gè)X的超類(lèi),那么到底是哪個(gè)超類(lèi)?不知道,但是可以肯定的說(shuō),Object一定是它的超類(lèi),所以get方法返回Object。編譯器是可以確定知道的。對(duì)于set方法來(lái)說(shuō),編譯器不知道它需要的確切類(lèi)型,但是X和X的子類(lèi)可以安全的轉(zhuǎn)型為X。
總結(jié):主要用于安全地寫(xiě)入數(shù)據(jù),可以寫(xiě)入X及其超類(lèi)型。

6、無(wú)限定的通配符 ?

表示對(duì)類(lèi)型沒(méi)有什么限制,可以把?看成所有類(lèi)型的父類(lèi),如Worker< ?>;
比如:
ArrayList<T> al=new ArrayList<T>(); 指定集合元素只能是T類(lèi)型
ArrayList<?> al=new ArrayList<?>();集合元素可以是任意類(lèi)型,這種沒(méi)有意義,一般是方法中,只是為了說(shuō)明用法。
在使用上:
? getFirst() : 返回值只能賦給 Object;
void setFirst(?) : setFirst 方法不能被調(diào)用, 甚至不能用 Object 調(diào)用;

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

泛型思想早在C++語(yǔ)言的模板(Template)中就開(kāi)始生根發(fā)芽,在Java語(yǔ)言處于還沒(méi)有出現(xiàn)泛型的版本時(shí),只能通過(guò)Object是所有類(lèi)型的父類(lèi)和類(lèi)型強(qiáng)制轉(zhuǎn)換兩個(gè)特點(diǎn)的配合來(lái)實(shí)現(xiàn)類(lèi)型泛化。,由于Java語(yǔ)言里面所有的類(lèi)型都繼承于java.lang.Object,所以O(shè)bject轉(zhuǎn)型成任何對(duì)象都是有可能的。但是也因?yàn)橛袩o(wú)限的可能性,就只有程序員和運(yùn)行期的虛擬機(jī)才知道這個(gè)Object到底是個(gè)什么類(lèi)型的對(duì)象。在編譯期間,編譯器無(wú)法檢查這個(gè)Object的強(qiáng)制轉(zhuǎn)型是否成功,如果僅僅依賴(lài)程序員去保障這項(xiàng)操作的正確性,許多ClassCastException的風(fēng)險(xiǎn)就會(huì)轉(zhuǎn)嫁到程序運(yùn)行期之中。

泛型技術(shù)在C#和Java之中的使用方式看似相同,但實(shí)現(xiàn)上卻有著根本性的分歧,C#里面泛型無(wú)論在程序源碼中、編譯后的IL中(Intermediate Language,中間語(yǔ)言,這時(shí)候泛型是一個(gè)占位符),或是運(yùn)行期的CLR中,都是切實(shí)存在的,List<int>與List<String>就是兩個(gè)不同的類(lèi)型,它們?cè)谙到y(tǒng)運(yùn)行期生成,有自己的虛方法表和類(lèi)型數(shù)據(jù),這種實(shí)現(xiàn)稱(chēng)為類(lèi)型膨脹,基于這種方法實(shí)現(xiàn)的泛型稱(chēng)為真實(shí)泛型。

Java語(yǔ)言中的泛型則不一樣,它只在程序源碼中存在,在編譯后的字節(jié)碼文件中,就已經(jīng)替換為原來(lái)的原生類(lèi)型(Raw Type,也稱(chēng)為裸類(lèi)型)了,并且在相應(yīng)的地方插入了強(qiáng)制轉(zhuǎn)型代碼,因此,對(duì)于運(yùn)行期的Java語(yǔ)言來(lái)說(shuō),ArrayList<int>與ArrayList<String>就是同一個(gè)類(lèi),所以泛型技術(shù)實(shí)際上是Java語(yǔ)言的一顆語(yǔ)法糖,Java語(yǔ)言中的泛型實(shí)現(xiàn)方法稱(chēng)為類(lèi)型擦除,基于這種方法實(shí)現(xiàn)的泛型稱(chēng)為偽泛型。

將一段Java代碼編譯成Class文件,然后再用字節(jié)碼反編譯工具進(jìn)行反編譯后,將會(huì)發(fā)現(xiàn)泛型都不見(jiàn)了,程序又變回了Java泛型出現(xiàn)之前的寫(xiě)法,泛型類(lèi)型都變回了原生類(lèi)型

image.png

上面這段代碼是不能被編譯的,因?yàn)閰?shù)List<Integer>和List<String>編譯之后都被擦除了,變成了一樣的原生類(lèi)型List<E>,擦除動(dòng)作導(dǎo)致這兩種方法的特征簽名變得一模一樣。

由于Java泛型的引入,各種場(chǎng)景(虛擬機(jī)解析、反射等)下的方法調(diào)用都可能對(duì)原有的基礎(chǔ)產(chǎn)生影響和新的需求,如在泛型類(lèi)中如何獲取傳入的參數(shù)化類(lèi)型等。因此,JCP組織對(duì)虛擬機(jī)規(guī)范做出了相應(yīng)的修改,引入了諸如Signature、LocalVariableTypeTable等新的屬性用于解決伴隨泛型而來(lái)的參數(shù)類(lèi)型的識(shí)別問(wèn)題,Signature是其中最重要的一項(xiàng)屬性,它的作用就是存儲(chǔ)一個(gè)方法在字節(jié)碼層面的特征簽名[3],這個(gè)屬性中保存的參數(shù)類(lèi)型并不是原生類(lèi)型,而是包括了參數(shù)化類(lèi)型的信息。修改后的虛擬機(jī)規(guī)范要求所有能識(shí)別49.0以上版本的Class文件的虛擬機(jī)都要能正確地識(shí)別Signature參數(shù)。

另外,從Signature屬性的出現(xiàn)我們還可以得出結(jié)論,擦除法所謂的擦除,僅僅是對(duì)方法的Code屬性中的字節(jié)碼進(jìn)行擦除,實(shí)際上元數(shù)據(jù)中還是保留了泛型信息,這也是我們能通過(guò)反射手段取得參數(shù)化類(lèi)型的根本依據(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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