欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

精通Java泛型的使用與原理

 更新時(shí)間:2022年04月06日 16:34:27   作者:nogos  
針對(duì)利用繼承來(lái)實(shí)現(xiàn)通用程序設(shè)計(jì)所產(chǎn)生的問(wèn)題,泛型提供了更好的解決方案,本文詳細(xì)的介紹了Java泛型的使用與原理,感興趣的可以了解一下

泛型之前

在面向?qū)ο缶幊陶Z(yǔ)言中,多態(tài)算是一種泛化機(jī)制。例如,你可以將方法的參數(shù)類型設(shè)置為基類,那么該方法就可以接受從這個(gè)基類中導(dǎo)出的任何類作為參數(shù),這樣的方法將會(huì)更具有通用性。此外,如果將方法參數(shù)聲明為接口,將會(huì)更加靈活。

通過(guò)繼承設(shè)計(jì)通用程序

在Java增加泛型類型之前,通用程序的設(shè)計(jì)就是利用繼承實(shí)現(xiàn)的,例如,ArrayList類只維護(hù)一個(gè)Object引用的數(shù)組,Object為所有類基類。

public class BeforeGeneric {
    /**
     * 泛型之前的通用程序設(shè)計(jì)
     */
    static class ArrayList{
        private Object[] elements=new Object[0];
        public Object get(int i){
            return elements[i];
        }
        /**
         * 這里的實(shí)現(xiàn),只是為了演示,不具有任何參考價(jià)值
         * */
        public void add(Object o){
            int length = elements.length;
            Object[] newElements = new Object[length+1];
            for(int i=0; i<length; i++){
                newElements[i] = elements[i];
            }
            newElements[length] = o;
            elements = newElements;
        }
    }
 
    public static void main(String[] args) {
        ArrayList stringValues = new ArrayList();
        //可以向數(shù)組中添加任何類型的對(duì)象
        stringValues.add(1);
        //問(wèn)題1——獲取值時(shí)必須強(qiáng)制轉(zhuǎn)換
        String str = (String) stringValues.get(0);
        //問(wèn)題2——上述強(qiáng)制轉(zhuǎn)型編譯時(shí)不會(huì)出錯(cuò),而運(yùn)行時(shí)報(bào)異常java.lang.ClassCastException
        System.out.println(str);
    }
}

面臨的問(wèn)題

  • 當(dāng)我們獲取一個(gè)值的時(shí)候,必須進(jìn)行強(qiáng)制類型轉(zhuǎn)換。
  • 當(dāng)我們插入一個(gè)值的時(shí)候,無(wú)法約束預(yù)期的類型。假定我們預(yù)想的是利用stringValues來(lái)存放String集合,因?yàn)锳rrayList只是維護(hù)一個(gè)Object引用的數(shù)組,我們無(wú)法阻止將Integer類型(Object子類)的數(shù)據(jù)加入stringValues。然而,當(dāng)我們使用數(shù)據(jù)的時(shí)候,需要將獲取的Object對(duì)象轉(zhuǎn)換為我們期望的類型(String),如果向集合中添加了非預(yù)期的類型(如Integer),編譯時(shí)我們不會(huì)收到任何的錯(cuò)誤提示。但當(dāng)我們運(yùn)行程序時(shí)卻會(huì)報(bào)異常:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at generic.BeforeGeneric.main(BeforeGeneric.java:24)

這顯然不是我們所期望的,如果程序有潛在的錯(cuò)誤,我們更期望在編譯時(shí)被告知錯(cuò)誤,而不是在運(yùn)行時(shí)報(bào)異常。

泛型

針對(duì)利用繼承來(lái)實(shí)現(xiàn)通用程序設(shè)計(jì)所產(chǎn)生的問(wèn)題,泛型提供了更好的解決方案:類型參數(shù)。例如,ArrayList類用一個(gè)類型參數(shù)來(lái)指出元素的類型。

ArrayList<String> stringValues=new ArrayList<String>();

這樣的代碼具有更好的可讀性,我們一看就知道該集合用來(lái)保存String類型的對(duì)象,而不是僅僅依賴變量名稱來(lái)暗示我們期望的類型。

public class GenericType {
    public static void main(String[] args) {  
        ArrayList<String> stringValues=new ArrayList<String>();
        stringValues.add("str");
        stringValues.add(1); //編譯錯(cuò)誤
    } 
}

現(xiàn)在,如果我們向ArrayList<String>添加Integer類型的對(duì)象,將會(huì)出現(xiàn)編譯錯(cuò)誤。

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
The method add(int, String) in the type ArrayList<String> is not applicable for the arguments (int)
at generic.GenericType.main(GenericType.java:8)

編譯器會(huì)自動(dòng)幫我們檢查,避免向集合中插入錯(cuò)誤類型的對(duì)象,從而使得程序具有更好的安全性。

總之,泛型通過(guò)類型參數(shù)使得我們的程序具有更好的可讀性和安全性。

Java泛型的實(shí)現(xiàn)原理

擦除

public class GenericType {
    public static void main(String[] args) {  
        ArrayList<String> arrayString=new ArrayList<String>();   
        ArrayList<Integer> arrayInteger=new ArrayList<Integer>();   
        System.out.println(arrayString.getClass()==arrayInteger.getClass());  
    }  
}

輸出:

true

在這個(gè)例子中,我們定義了兩個(gè)ArrayList數(shù)組,不過(guò)一個(gè)是ArrayList<String>泛型類型,只能存儲(chǔ)字符串。一個(gè)是ArrayList<Integer>泛型類型,只能存儲(chǔ)整型。最后,我們通過(guò)arrayString對(duì)象和arrayInteger對(duì)象的getClass方法獲取它們的類信息并比較,發(fā)現(xiàn)結(jié)果為true。

這是為什么呢,明明我們定義了兩種不同的類型?因?yàn)?,在編譯期間,所有的泛型信息都會(huì)被擦除,List<Integer>和List<String>類型,在編譯后都會(huì)變成List類型(原始類型)。Java中的泛型基本上都是在編譯器這個(gè)層次來(lái)實(shí)現(xiàn)的,這也是Java的泛型被稱為“偽泛型”的原因。

原始類型

原始類型就是泛型類型擦除了泛型信息后,在字節(jié)碼中真正的類型。無(wú)論何時(shí)定義一個(gè)泛型類型,相應(yīng)的原始類型都會(huì)被自動(dòng)提供。原始類型的名字就是刪去類型參數(shù)后的泛型類型的類名。擦除 類型變量,并替換為 限定類型(T為無(wú)限定的 類型變量,用Object替換)。

//泛型類型
class Pair<T> {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T  value) {  
        this.value = value;  
    }  
}
//原始類型
class Pair {  
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
}  

因?yàn)樵赑air<T>中,T是一個(gè)無(wú)限定的類型變量,所以用Object替換。如果是Pair<T extends Number>,擦除后,類型變量用Number類型替換。

突破泛型約束

public class ReflectInGeneric {
    public static void main(String[] args) throws IllegalArgumentException, 
                        SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {  
        ArrayList<Integer> array=new ArrayList<Integer>();  
        // 這樣調(diào)用add方法只能存儲(chǔ)整形,因?yàn)榉盒皖愋偷膶?shí)例為Integer 
        array.add(1); 
        // 通過(guò)泛型可以突破泛型類型約束
        array.getClass().getMethod("add", Object.class).invoke(array, "asd");  
        for (int i=0;i<array.size();i++) {  
            System.out.println(array.get(i));  
        }  
    }  
}

輸出:

1
asd

為什么呢?我們?cè)诮榻B泛型時(shí)指出向ArrayList<Integer>中插入String類型的對(duì)象,編譯時(shí)會(huì)報(bào)錯(cuò)?,F(xiàn)在為什么又可以了呢?

  • 我們?cè)诔绦蛑卸x了一個(gè)ArrayList<Integer>泛型類型,如果直接調(diào)用add方法,那么只能存儲(chǔ)整形的數(shù)據(jù)。
  • 不過(guò)當(dāng)我們利用反射調(diào)用add方法的時(shí)候,卻可以存儲(chǔ)字符串。

這說(shuō)明ArrayList<Integer>泛型信息在編譯之后被擦除了,只保留了原始類型,類型變量(T)被替換為Object,在運(yùn)行時(shí),我們可以行其中插入任意類型的對(duì)象。

再次應(yīng)證:Java中的泛型基本上都是在編譯器這個(gè)層次來(lái)實(shí)現(xiàn)的“偽泛型”。

但是,并不推薦以這種方式操作泛型類型,因?yàn)檫@違背了泛型的初衷(減少?gòu)?qiáng)制類型轉(zhuǎn)換以及確保類型安全)。當(dāng)我們從集合中獲取元素時(shí),默認(rèn)會(huì)將對(duì)象強(qiáng)制轉(zhuǎn)換成泛型參數(shù)指定的類型(這里是Integer),如果放入了非法的對(duì)象這個(gè)強(qiáng)制轉(zhuǎn)換過(guò)程就會(huì)出現(xiàn)異常。

泛型方法的類型推斷

在調(diào)用泛型方法的時(shí)候,可以指定泛型類型,也可以不指定。

在不指定泛型類型的情況下,泛型類型為該方法中的幾種參數(shù)類型的共同父類的最小級(jí),直到Object。

在指定泛型類型的時(shí)候,該方法中的所有參數(shù)類型必須是該泛型類型或者其子類。

public class Test { 
    public static void main(String[] args) {  
        /**不指定泛型的時(shí)候*/  
        int i=Test.add(1, 2); //這兩個(gè)參數(shù)都是Integer,所以T替換為Integer類型  
        Number f=Test.add(1, 1.2);//這兩個(gè)參數(shù)一個(gè)是Integer,另一個(gè)是Float,所以取同一父類的最小級(jí),為Number  
        Object o=Test.add(1, "asd");//這兩個(gè)參數(shù)一個(gè)是Integer,另一個(gè)是String,所以取同一父類的最小級(jí),為Object
  
        /**指定泛型的時(shí)候*/  
        int a=Test.<Integer>add(1, 2);//指定了Integer,所以只能為Integer類型或者其子類  
        int b=Test.<Integer>add(1, 2.2);//編譯錯(cuò)誤,指定了Integer,不能為Float  
        Number c=Test.<Number>add(1, 2.2); //指定為Number,所以可以為Integer和Float  
    }  
      
    //這是一個(gè)簡(jiǎn)單的泛型方法  
    public static <T> T add(T x,T y){  
        return y;  
    } 
}

正確的運(yùn)轉(zhuǎn)

既然說(shuō)類型變量會(huì)在編譯的時(shí)候擦除掉,那為什么定義了ArrayList<Integer>泛型類型,而不允許向其中插入String對(duì)象呢?不是說(shuō)泛型變量Integer會(huì)在編譯時(shí)候擦除變?yōu)樵碱愋蚈bject嗎,為什么不能存放別的類型呢?既然類型擦除了,如何保證我們只能使用泛型變量限定的類型呢?
java是如何解決這個(gè)問(wèn)題的呢?java編譯器是通過(guò)先檢查代碼中泛型的類型,然后再進(jìn)行類型擦除,再進(jìn)行編譯的。以如下代碼為例:

        Pair<Integer> pair=new Pair<Integer> ();
        pair.setValue(3);
        Integer integer=pair.getValue();
        System.out.println(integer);

擦除getValue()的返回類型后將返回Object類型,編譯器自動(dòng)插入Integer的強(qiáng)制類型轉(zhuǎn)換。也就是說(shuō),編譯器把這個(gè)方法調(diào)用翻譯為兩條字節(jié)碼指令:

  • 對(duì)原始方法Pair.getValue的調(diào)用
  • 將返回的Object類型強(qiáng)制轉(zhuǎn)換為Integer

此外,存取一個(gè)泛型域時(shí),也要插入強(qiáng)制類型轉(zhuǎn)換。因此,我們說(shuō)Java的泛型是在編譯器層次進(jìn)行實(shí)現(xiàn)的,被稱為“偽泛型”,相對(duì)于C++。

泛型相關(guān)問(wèn)題

1、泛型類型引用傳遞問(wèn)題

在Java中,像下面形式的引用傳遞是不允許的:

ArrayList<String> arrayList1=new ArrayList<Object>();//編譯錯(cuò)誤  
ArrayList<Object> arrayList1=new ArrayList<String>();//編譯錯(cuò)誤 

我們先看第一種情況,將第一種情況拓展成下面的形式:

ArrayList<Object> arrayList1=new ArrayList<Object>();  
arrayList1.add(new Object());  
arrayList1.add(new Object());  
ArrayList<String> arrayList2=arrayList1;//編譯錯(cuò)誤  

實(shí)際上,在第4行代碼處,就會(huì)有編譯錯(cuò)誤。那么,我們先假設(shè)它編譯沒(méi)錯(cuò)。那么當(dāng)我們使用arrayList2引用用get()方法取值的時(shí)候,返回的都是String類型的對(duì)象,可是它里面實(shí)際上已經(jīng)被我們存放了Object類型的對(duì)象,這樣,就會(huì)有ClassCastException了。所以為了避免這種極易出現(xiàn)的錯(cuò)誤,Java不允許進(jìn)行這樣的引用傳遞。(這也是泛型出現(xiàn)的原因,就是為了解決類型轉(zhuǎn)換的問(wèn)題,我們不能違背它的初衷)。
在看第二種情況,將第二種情況拓展成下面的形式:

ArrayList<String> arrayList1=new ArrayList<String>();  
arrayList1.add(new String());  
arrayList1.add(new String());  
ArrayList<Object> arrayList2=arrayList1;//編譯錯(cuò)誤  

沒(méi)錯(cuò),這樣的情況比第一種情況好的多,最起碼,在我們用arrayList2取值的時(shí)候不會(huì)出現(xiàn)ClassCastException,因?yàn)槭菑腟tring轉(zhuǎn)換為Object??墒牵@樣做有什么意義呢,泛型出現(xiàn)的原因,就是為了解決類型轉(zhuǎn)換的問(wèn)題。我們使用了泛型,到頭來(lái),還是要自己強(qiáng)轉(zhuǎn),違背了泛型設(shè)計(jì)的初衷。所以java不允許這么干。再說(shuō),你如果又用arrayList2往里面add()新的對(duì)象,那么到時(shí)候取得時(shí)候,我怎么知道我取出來(lái)的到底是String類型的,還是Object類型的呢?
所以,要格外注意泛型中引用傳遞問(wèn)題。

2、泛型類型變量不能是基本數(shù)據(jù)類型

就比如,沒(méi)有ArrayList<double>,只有ArrayList<Double>。因?yàn)楫?dāng)類型擦除后,ArrayList的原始類中的類型變量(T)替換為Object,但Object類型不能存儲(chǔ)double值。

3、運(yùn)行時(shí)類型查詢

舉個(gè)例子:

ArrayList<String> arrayList=new ArrayList<String>();    

因?yàn)轭愋筒脸螅珹rrayList<String>只剩下原始類型,泛型信息String不存在了。那么,運(yùn)行時(shí)進(jìn)行類型查詢的時(shí)候使用下面的方法是錯(cuò)誤的

if( arrayList instanceof ArrayList<String>)    
java限定了這種類型查詢的方式,?為通配符,也即非限定符。

if( arrayList instanceof ArrayList<?>)    

4、泛型在靜態(tài)方法和靜態(tài)類中的問(wèn)題

泛型類中的靜態(tài)方法和靜態(tài)變量不可以使用泛型類所聲明的泛型類型參數(shù)

public class Test2<T> {    
    public static T one;   //編譯錯(cuò)誤    
    public static  T show(T one){ //編譯錯(cuò)誤    
        return null;    
    }    
}  

因?yàn)榉盒皖愔械姆盒蛥?shù)的實(shí)例化是在定義泛型類型對(duì)象(例如ArrayList<Integer>)的時(shí)候指定的,而靜態(tài)變量和靜態(tài)方法不需要使用對(duì)象來(lái)調(diào)用。對(duì)象都沒(méi)有創(chuàng)建,如何確定這個(gè)泛型參數(shù)是何種類型,所以當(dāng)然是錯(cuò)誤的。

但是要注意區(qū)分下面的一種情況:

public class Test2<T> {    
    public static <T >T show(T one){//這是正確的    
        return null;    
    }    
}  

因?yàn)檫@是一個(gè)泛型方法,在泛型方法中使用的T是自己在方法中定義的T,而不是泛型類中的T。

泛型相關(guān)面試題

1. Java中的泛型是什么 ? 使用泛型的好處是什么?

泛型是一種參數(shù)化類型的機(jī)制。它可以使得代碼適用于各種類型,從而編寫(xiě)更加通用的代碼,例如集合框架。

泛型是一種編譯時(shí)類型確認(rèn)機(jī)制。它提供了編譯期的類型安全,確保在泛型類型(通常為泛型集合)上只能使用正確類型的對(duì)象,避免了在運(yùn)行時(shí)出現(xiàn)ClassCastException。

2、Java的泛型是如何工作的 ? 什么是類型擦除 ?

泛型的正常工作是依賴編譯器在編譯源碼的時(shí)候,先進(jìn)行類型檢查,然后進(jìn)行類型擦除并且在類型參數(shù)出現(xiàn)的地方插入強(qiáng)制轉(zhuǎn)換的相關(guān)指令實(shí)現(xiàn)的。

編譯器在編譯時(shí)擦除了所有類型相關(guān)的信息,所以在運(yùn)行時(shí)不存在任何類型相關(guān)的信息。例如List<String>在運(yùn)行時(shí)僅用一個(gè)List類型來(lái)表示。為什么要進(jìn)行擦除呢?這是為了避免類型膨脹。

3. 什么是泛型中的限定通配符和非限定通配符 ?

限定通配符對(duì)類型進(jìn)行了限制。有兩種限定通配符,一種是<? extends T>它通過(guò)確保類型必須是T的子類來(lái)設(shè)定類型的上界,另一種是<? super T>它通過(guò)確保類型必須是T的父類來(lái)設(shè)定類型的下界。泛型類型必須用限定內(nèi)的類型來(lái)進(jìn)行初始化,否則會(huì)導(dǎo)致編譯錯(cuò)誤。另一方面<?>表示了非限定通配符,因?yàn)?lt;?>可以用任意類型來(lái)替代。

4. List<? extends T>和List <? super T>之間有什么區(qū)別 ?

這和上一個(gè)面試題有聯(lián)系,有時(shí)面試官會(huì)用這個(gè)問(wèn)題來(lái)評(píng)估你對(duì)泛型的理解,而不是直接問(wèn)你什么是限定通配符和非限定通配符。這兩個(gè)List的聲明都是限定通配符的例子,List<? extends T>可以接受任何繼承自T的類型的List,而List<? super T>可以接受任何T的父類構(gòu)成的List。例如List<? extends Number>可以接受List<Integer>或List<Float>。

5. 如何編寫(xiě)一個(gè)泛型方法,讓它能接受泛型參數(shù)并返回泛型類型?

編寫(xiě)泛型方法并不困難,你需要用泛型類型來(lái)替代原始類型,比如使用T, E or K,V等被廣泛認(rèn)可的類型占位符。泛型方法的例子請(qǐng)參閱Java集合類框架。最簡(jiǎn)單的情況下,一個(gè)泛型方法可能會(huì)像這樣:

public V put(K key, V value) {
    return cache.put(key, value);
}

6. Java中如何使用泛型編寫(xiě)帶有參數(shù)的類?

這是上一道面試題的延伸。面試官可能會(huì)要求你用泛型編寫(xiě)一個(gè)類型安全的類,而不是編寫(xiě)一個(gè)泛型方法。關(guān)鍵仍然是使用泛型類型來(lái)代替原始類型,而且要使用JDK中采用的標(biāo)準(zhǔn)占位符。

7. 編寫(xiě)一段泛型程序來(lái)實(shí)現(xiàn)LRU緩存?

對(duì)于喜歡Java編程的人來(lái)說(shuō)這相當(dāng)于是一次練習(xí)。給你個(gè)提示,LinkedHashMap可以用來(lái)實(shí)現(xiàn)固定大小的LRU緩存,當(dāng)LRU緩存已經(jīng)滿了的時(shí)候,它會(huì)把最老的鍵值對(duì)移出緩存。LinkedHashMap提供了一個(gè)稱為removeEldestEntry()的方法,該方法會(huì)被put()和putAll()調(diào)用來(lái)刪除最老的鍵值對(duì)。

8. 你可以把List<String>傳遞給一個(gè)接受List<Object>參數(shù)的方法嗎?

對(duì)任何一個(gè)不太熟悉泛型的人來(lái)說(shuō),這個(gè)Java泛型題目看起來(lái)令人疑惑,因?yàn)檎Э雌饋?lái)String是一種Object,所以List<String>應(yīng)當(dāng)可以用在需要List<Object>的地方,但是事實(shí)并非如此。真這樣做的話會(huì)導(dǎo)致編譯錯(cuò)誤。如果你再深一步考慮,你會(huì)發(fā)現(xiàn)Java這樣做是有意義的,因?yàn)長(zhǎng)ist<Object>可以存儲(chǔ)任何類型的對(duì)象包括String, Integer等等,而List<String>卻只能用來(lái)存儲(chǔ)Strings。

List<Object> objectList;
List<String> stringList;
      
objectList = stringList;  //compilation error incompatible types

9. Array中可以用泛型嗎?

這可能是Java泛型面試題中最簡(jiǎn)單的一個(gè)了,當(dāng)然前提是你要知道Array事實(shí)上并不支持泛型,這也是為什么Joshua Bloch在Effective Java一書(shū)中建議使用List來(lái)代替Array,因?yàn)長(zhǎng)ist可以提供編譯期的類型安全保證,而Array卻不能。

10. 如何阻止Java中的類型未檢查的警告?

如果你把泛型和原始類型混合起來(lái)使用,例如下列代碼,Java 5的javac編譯器會(huì)產(chǎn)生類型未檢查的警告
,例如List<String> rawList = new ArrayList()
注意: Hello.java使用了未檢查或稱為不安全的操作;
這種警告可以使用@SuppressWarnings("unchecked")注解來(lái)屏蔽。

11、Java中List<Object>和原始類型List之間的區(qū)別?

原始類型和帶參數(shù)類型<Object>之間的主要區(qū)別是,在編譯時(shí)編譯器不會(huì)對(duì)原始類型進(jìn)行類型安全檢查,卻會(huì)對(duì)帶參數(shù)的類型進(jìn)行檢查,通過(guò)使用Object作為類型,可以告知編譯器該方法可以接受任何類型的對(duì)象,比如String或Integer。這道題的考察點(diǎn)在于對(duì)泛型中原始類型的正確理解。它們之間的第二點(diǎn)區(qū)別是,你可以把任何帶參數(shù)的泛型類型傳遞給接受原始類型List的方法,但卻不能把List<String>傳遞給接受List<Object>的方法,因?yàn)闀?huì)產(chǎn)生編譯錯(cuò)誤。

12、Java中List<?>和List<Object>之間的區(qū)別是什么?

這道題跟上一道題看起來(lái)很像,實(shí)質(zhì)上卻完全不同。List<?> 是一個(gè)未知類型的List,而List<Object>其實(shí)是任意類型的List。你可以把List<String>, List<Integer>賦值給List<?>,卻不能把List<String>賦值給List<Object>。   

List<?> listOfAnyType;
List<Object> listOfObject = new ArrayList<Object>();
List<String> listOfString = new ArrayList<String>();
List<Integer> listOfInteger = new ArrayList<Integer>();
      
listOfAnyType = listOfString; //legal
listOfAnyType = listOfInteger; //legal
listOfObjectType = (List<Object>) listOfString; //compiler error - in-convertible types

13、List<String>和原始類型List之間的區(qū)別.

該題類似于“原始類型和帶參數(shù)類型之間有什么區(qū)別”。帶參數(shù)類型是類型安全的,而且其類型安全是由編譯器保證的,但原始類型List卻不是類型安全的。你不能把String之外的任何其它類型的Object存入String類型的List中,而你可以把任何類型的對(duì)象存入原始List中。使用泛型的帶參數(shù)類型你不需要進(jìn)行類型轉(zhuǎn)換,但是對(duì)于原始類型,你則需要進(jìn)行顯式的類型轉(zhuǎn)換。

List listOfRawTypes = new ArrayList();
listOfRawTypes.add("abc");
listOfRawTypes.add(123); //編譯器允許這樣 - 運(yùn)行時(shí)卻會(huì)出現(xiàn)異常
String item = (String) listOfRawTypes.get(0); //需要顯式的類型轉(zhuǎn)換
item = (String) listOfRawTypes.get(1); //拋ClassCastException,因?yàn)镮nteger不能被轉(zhuǎn)換為String
      
List<String> listOfString = new ArrayList();
listOfString.add("abcd");
listOfString.add(1234); //編譯錯(cuò)誤,比在運(yùn)行時(shí)拋異常要好
item = listOfString.get(0); //不需要顯式的類型轉(zhuǎn)換 - 編譯器自動(dòng)轉(zhuǎn)換

通配符

通配符上界

常規(guī)使用

public class Test {
    public static void printIntValue(List<? extends Number> list) {
        for (Number number : list) {
            System.out.print(number.intValue()+" "); 
        }
        System.out.println();
    }
    public static void main(String[] args) {
        List<Integer> integerList=new ArrayList<Integer>();
        integerList.add(2);
        integerList.add(2);
        printIntValue(integerList);
        List<Float> floatList=new ArrayList<Float>();
        floatList.add((float) 3.3);
        floatList.add((float) 0.3);
        printIntValue(floatList);
    }
}

輸出:
2 2 
3 0 

非法使用

public class Test {
    public static void fillNumberList(List<? extends Number> list) {
        list.add(new Integer(0));//編譯錯(cuò)誤
        list.add(new Float(1.0));//編譯錯(cuò)誤
    }
    public static void main(String[] args) {
        List<? extends Number> list=new ArrayList();
        list.add(new Integer(1));//編譯錯(cuò)誤
        list.add(new Float(1.0));//編譯錯(cuò)誤
    }
}

List<? extends Number>可以代表List<Integer>或List<Float>,為什么不能像其中加入Integer或者Float呢?

首先,我們知道List<Integer>之中只能加入Integer。并且如下代碼是可行的:

        List<? extends Number> list1=new ArrayList<Integer>();
        List<? extends Number> list2=new ArrayList<Float>();

假設(shè)前面的例子沒(méi)有編譯錯(cuò)誤,如果我們把list1或者list2傳入方法fillNumberList,顯然都會(huì)出現(xiàn)類型不匹配的情況, 假設(shè)不成立。
因此,我們得出結(jié)論:不能往List<? extends T> 中添加任意對(duì)象,除了null。

那為什么對(duì)List<? extends T>進(jìn)行迭代可以呢,因?yàn)樽宇惐囟ㄓ懈割愊嗤慕涌?,這正是我們所期望的。

通配符下界

常規(guī)使用

public class Test {
    public static void fillNumberList(List<? super Number> list) {
        list.add(new Integer(0));
        list.add(new Float(1.0));
    }
    public static void main(String[] args) {
        List<? super Number> list=new ArrayList(); 
        list.add(new Integer(1));
        list.add(new Float(1.1));
    }
}

可以添加Number的任何子類,為什么呢?
List<? super Number>可以代表List<T>,其中T為Number父類,(雖然Number沒(méi)有父類)。如果說(shuō),T為Number的父類,我們想List<T>中加入Number的子類肯定是可以的。

非法使用

對(duì)List<? superT>進(jìn)行迭代是不允許的。為什么呢?你知道用哪種接口去迭代List嗎?只有用Object類的接口才能保證集合中的元素都擁有該接口,顯然這個(gè)意義不大。其應(yīng)用場(chǎng)景略。

無(wú)界通配符

知道了通配符的上界和下界,其實(shí)也等同于知道了無(wú)界通配符,不加任何修飾即可,單獨(dú)一個(gè)“?”。如List<?>,“?”可以代表任意類型,“任意”也就是未知類型。
List<Object>與List<?>并不等同,List<Object>是List<?>的子類。還有不能往List<?> list里添加任意對(duì)象,除了null。

常規(guī)使用

1、當(dāng)方法是使用原始的Object類型作為參數(shù)時(shí),如下:

public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + "");
    System.out.println();
}

可以選擇改為如下實(shí)現(xiàn):

public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + "");
    System.out.println();
}

這樣就可以兼容更多的輸出,而不單純是List<Object>,如下:

List<Integer> li = Arrays.asList(1, 2, 3);
List<String>  ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);

參考:

《Java核心技術(shù) 卷一》

http://blog.csdn.net/lonelyroamer/article/details/7868820

http://www.oschina.net/translate/10-interview-questions-on-java-generics

http://www.linuxidc.com/Linux/2013-10/90928.htm

到此這篇關(guān)于精通Java泛型的使用與原理的文章就介紹到這了,更多相關(guān)Java 泛型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring4整合Hibernate5詳細(xì)步驟

    Spring4整合Hibernate5詳細(xì)步驟

    本篇文章主要介紹了Spring4整合Hibernate5詳細(xì)步驟,具有一定的參考價(jià)值,有興趣的同學(xué)可以了解一下
    2017-04-04
  • java調(diào)用webservice的.asmx接口的使用步驟

    java調(diào)用webservice的.asmx接口的使用步驟

    這篇文章主要介紹了java調(diào)用webservice的.asmx接口的使用步驟,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09
  • Java?數(shù)據(jù)結(jié)構(gòu)深入理解ArrayList與順序表

    Java?數(shù)據(jù)結(jié)構(gòu)深入理解ArrayList與順序表

    ArrayList?類是一個(gè)可以動(dòng)態(tài)修改的數(shù)組,與普通數(shù)組的區(qū)別就是它是沒(méi)有固定大小的限制,我們可以添加或刪除元素。ArrayList?繼承了?AbstractList?,并實(shí)現(xiàn)了?List?接口,順序表是將元素順序地存放在一塊連續(xù)的存儲(chǔ)區(qū)里,元素間的順序關(guān)系由它們的存儲(chǔ)順序自然表示
    2022-04-04
  • 淺談java實(shí)現(xiàn)背包算法(0-1背包問(wèn)題)

    淺談java實(shí)現(xiàn)背包算法(0-1背包問(wèn)題)

    本篇文章主要介紹了淺談java實(shí)現(xiàn)背包算法(0-1背包問(wèn)題) ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-08-08
  • Redisson可重入鎖解鎖邏輯詳細(xì)講解

    Redisson可重入鎖解鎖邏輯詳細(xì)講解

    Redisson開(kāi)源框架是一個(gè)Redis的分布式鎖的現(xiàn)成實(shí)現(xiàn)方案,是Redis的java實(shí)現(xiàn)的客戶端。通過(guò)Netty支持非阻塞I/O。Redisson實(shí)現(xiàn)了分布式鎖的自動(dòng)續(xù)期機(jī)制、鎖的互斥自等待機(jī)制、鎖的可重入加鎖與釋放鎖的機(jī)制
    2023-02-02
  • Java NIO Selector用法詳解【含多人聊天室實(shí)例】

    Java NIO Selector用法詳解【含多人聊天室實(shí)例】

    這篇文章主要介紹了Java NIO Selector用法,結(jié)合實(shí)例形式分析了Java NIO Selector基本功能、原理與使用方法,并結(jié)合了多人聊天室實(shí)例加以詳細(xì)說(shuō)明,需要的朋友可以參考下
    2019-11-11
  • java并發(fā)容器CopyOnWriteArrayList實(shí)現(xiàn)原理及源碼分析

    java并發(fā)容器CopyOnWriteArrayList實(shí)現(xiàn)原理及源碼分析

    這篇文章主要為大家詳細(xì)介紹了java并發(fā)容器CopyOnWriteArrayList實(shí)現(xiàn)原理及源碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • 基于JavaMail API收發(fā)郵件的方法

    基于JavaMail API收發(fā)郵件的方法

    這篇文章主要介紹了基于JavaMail API收發(fā)郵件的方法,實(shí)例分析了javamail的使用方法與相關(guān)注意事項(xiàng),非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-07-07
  • 一文搞懂?MyBatis的事務(wù)管理機(jī)制

    一文搞懂?MyBatis的事務(wù)管理機(jī)制

    MyBatis是一款優(yōu)秀的持久層框架,相信很多Java后端開(kāi)發(fā)人員對(duì)它都不會(huì)陌生。本文將從事務(wù)概述、MyBatis實(shí)現(xiàn)事務(wù)的方式、事務(wù)實(shí)現(xiàn)源碼分析方面詳細(xì)解析MyBatis的事務(wù)管理機(jī)制,需要的朋友可以參考下
    2023-05-05
  • 如何測(cè)試Java類的線程安全性

    如何測(cè)試Java類的線程安全性

    這篇文章主要介紹了如何測(cè)試Java類的線程安全性,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12

最新評(píng)論