JAVA基礎(chǔ)之泛型

1、為什么引入泛型

bug是編程的一部分,我們只能盡自己最大的能力減少出現(xiàn)bug的幾率,但是誰(shuí)也不能保證自己寫(xiě)出的程序不出現(xiàn)任何問(wèn)題开瞭。

錯(cuò)誤可分為兩種:編譯時(shí)錯(cuò)誤運(yùn)行時(shí)錯(cuò)誤懒震。編譯時(shí)錯(cuò)誤在編譯時(shí)可以發(fā)現(xiàn)并排除,而運(yùn)行時(shí)錯(cuò)誤具有很大的不確定性惩阶,在程序運(yùn)行時(shí)才能發(fā)現(xiàn)挎狸,造成的后果可能是災(zāi)難性的。

使用泛型可以使錯(cuò)誤在編譯時(shí)被探測(cè)到断楷,從而增加程序的健壯性锨匆。

來(lái)看一個(gè)例子:

    public class Box {
        private Object object;

        public void set(Object object) {
            this.object = object;
        }

        public Object get() {
            return object;
        }
    }

按照聲明,其中的set()方法可以接受任何java對(duì)象作為參數(shù)(任何對(duì)象都是Object的子類(lèi))冬筒,假如在某個(gè)地方使用該類(lèi)恐锣,set()方法預(yù)期的輸入對(duì)象為Integer類(lèi)型,但是實(shí)際輸入的卻是String類(lèi)型舞痰,就會(huì)拋出一個(gè)運(yùn)行時(shí)錯(cuò)誤土榴,這個(gè)錯(cuò)誤在編譯階段是無(wú)法檢測(cè)的。例如:

Box box = new Box;  
box.set("abc");  
Integer a = (Integer)box.get();  //編譯時(shí)不會(huì)報(bào)錯(cuò)响牛,但是運(yùn)行時(shí)會(huì)報(bào)ClassCastException

運(yùn)用泛型改造上面的代碼:

public class Box<T> {

    private T t;

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }
}

當(dāng)我們使用該類(lèi)時(shí)會(huì)指定T的具體類(lèi)型玷禽,該類(lèi)型參數(shù)可以是類(lèi)、接口呀打、數(shù)組等矢赁,但是不能是基本類(lèi)型

比如:

Box<Integer> box = new Box<Integer>;  //指定了類(lèi)型類(lèi)型為Integer
//box.set(“abc”);  該句在編譯時(shí)就會(huì)報(bào)錯(cuò)
box.set(new Integer(2));
Integer a = box.get();  //不用轉(zhuǎn)換類(lèi)型

可以看到贬丛,使用泛型還免除轉(zhuǎn)換操作撩银。

在引入泛型機(jī)制之前,要在方法中支持多個(gè)數(shù)據(jù)類(lèi)型豺憔,需要對(duì)方法進(jìn)行重載额获,在引入范型后,可以更簡(jiǎn)潔地解決此問(wèn)題恭应,更進(jìn)一步可以定義多個(gè)參數(shù)以及返回值之間的關(guān)系抄邀。

例如

public void write(Integer i, Integer[] ia);
public void write(Double  d, Double[] da);
public void write(Long l, Long[] la);

范型版本為:

public <T> void write(T t, T[] ta);

總體來(lái)說(shuō),泛型機(jī)制能夠在定義類(lèi)昼榛、接口撤摸、方法時(shí)把“類(lèi)型”當(dāng)做參數(shù)使用,有點(diǎn)類(lèi)似于方法聲明中的形式參數(shù)褒纲,如此我們就能通過(guò)不同的輸入?yún)?shù)來(lái)實(shí)現(xiàn)程序的重用。不同的是钥飞,形式參數(shù)的輸入是值莺掠,而泛型參數(shù)的輸入是類(lèi)型。

2读宙、命名規(guī)則

類(lèi)型參數(shù)的命名有一套默認(rèn)規(guī)則彻秆,為了提高代碼的維護(hù)性和可讀性,強(qiáng)烈建議遵循這些規(guī)則。JDK中唇兑,隨處可見(jiàn)這些命名規(guī)則的應(yīng)用酒朵。

  • E - Element (通常代表集合類(lèi)中的元素)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. – 第二個(gè),第三個(gè)扎附,第四個(gè)類(lèi)型參數(shù)……

注意蔫耽,父類(lèi)定義的類(lèi)型參數(shù)不能被子類(lèi)繼承

也可以同時(shí)聲明多個(gè)類(lèi)型變量留夜,用逗號(hào)分割匙铡,例如:

public interface Pair<K, V> {
    public K getKey();
    public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {

    private K key;
    private V value;

    public OrderedPair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }
  
    public V getValue() {
        return value;
    }
}

下面的兩行代碼創(chuàng)建了OrderedPair對(duì)象的兩個(gè)實(shí)例。

Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String>  p2 = new OrderedPair<String, String>("hello", "world");

//也可以將new后面的類(lèi)型參數(shù)省略碍粥,簡(jiǎn)寫(xiě)為:
//Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);

//也可以在尖括號(hào)內(nèi)使用帶有類(lèi)型變量的類(lèi)型變量鳖眼,例如:
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));

泛型是JDK 5.0之后才引入的,為了兼容性嚼摩,允許不指定泛型參數(shù)钦讳,但是如此一來(lái),編譯器就無(wú)法進(jìn)行類(lèi)型檢查枕面,在編程時(shí)愿卒,最好明確指定泛型參數(shù)。

同樣膊畴,在方法中也可是使用泛型參數(shù)掘猿,并且該參數(shù)的使用范圍僅限于方法體內(nèi)。例如:

public class Util {
        //該方法用于比較兩個(gè)Pair對(duì)象是否相等唇跨。
        //泛型參數(shù)必須寫(xiě)在方法返回類(lèi)型boolean之前
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);
//實(shí)際上稠通,編譯器可以通過(guò)Pair當(dāng)中的類(lèi)型來(lái)推斷compare需要使用的類(lèi)型,所以可以簡(jiǎn)寫(xiě)為:
// boolean same = Util. compare(p1, p2);

有時(shí)候我們想讓類(lèi)型參數(shù)限定在某個(gè)范圍之內(nèi)买猖,就需要用到extends關(guān)鍵字(extends后面可以跟一個(gè)接口改橘,這里的extends既可以表示繼承了某個(gè)類(lèi),也可以表示實(shí)現(xiàn)了某個(gè)接口)玉控,例如飞主,我們想讓參數(shù)是數(shù)字類(lèi)型:

    class Box<T extends Number> {  //類(lèi)型參數(shù)限定為Number的子類(lèi)

        private T t;

        public Box(T t) {
            this.t = t;
        }

        public void print() {
            System.out.println(t.getClass().getName());
        }

        public static void main(String[] args) {

            Box<Integer> box1 = new Box<Integer>(new Integer(2));
            box1.print();  //打印結(jié)果:java.lang.Integer
            Box<Double> box2 = new Box<Double>(new Double(1.2));
            box2.print();  //打印結(jié)果:java.lang.Double

            Box<String> box2 = new Box<String>(new String("abc"));  //報(bào)錯(cuò),因?yàn)镾tring類(lèi)型不是Number的子類(lèi)
            box2.print();
        }

    } 

如果加入多個(gè)限定高诺,可以用“&”連接起來(lái)碌识,但是由于java是單繼承,多個(gè)限定中最多只能有一個(gè)類(lèi)虱而,而且必須放在第一個(gè)位置筏餐。例如:

    class Box<T extends Number & Cloneable & Comparable> {

        //該類(lèi)型必須為Number的子類(lèi)并且實(shí)現(xiàn)了Cloneable接口和Comparable接口。
        //……
    }

3牡拇、泛型類(lèi)的繼承

java是面向?qū)ο蟮母呒?jí)語(yǔ)言魁瞪,在一個(gè)接受A類(lèi)參數(shù)的地方傳入一個(gè)A的子類(lèi)是允許的穆律,例如:

Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger;   // 因?yàn)镮nteger是Object的子類(lèi)

這種特性同樣適用于類(lèi)型參數(shù),例如:

Box<Number> box = new Box<Number>();
box.add(new Integer(10));   // Integer是Number的子類(lèi)
box.add(new Double(10.1));  // Double同樣是Number的子類(lèi)

但是导俘,有一種情況很容易引起混淆峦耘,例如:

//該方法接受的參數(shù)類(lèi)型為Box<Number>
public void boxTest(Box<Number> n) { 
    //……
}
//下面兩種調(diào)用都會(huì)報(bào)錯(cuò)
boxTest(Box<Integer>);
boxTest(Box<Double>);

雖然Integer和Double都是Number的子類(lèi),但是Box<Integer>與Box<Double>并不是Box<Number>的子類(lèi)旅薄,不存在繼承關(guān)系辅髓。Box<Integer>與Box<Double>的共同父類(lèi)是Object。

1.png

以JDK中的集合類(lèi)為例赋秀,ArrayList<E> 實(shí)現(xiàn)了 List<E>接口利朵,List<E>接口繼承了 Collection<E>接口,所以猎莲,ArrayList<String>是List<String>的子類(lèi)绍弟,而非List<Integer>的子類(lèi)。三者的繼承關(guān)系如下:

2.png

4著洼、類(lèi)型推斷

先來(lái)看一個(gè)例子:

    public class Demo {

        static <T> T pick(T a1, T a2) {
            return a2;
        }

    }

靜態(tài)方法pick()在三個(gè)地方使用了泛型樟遣,分別限定了兩個(gè)輸入?yún)?shù)的類(lèi)型與返回類(lèi)型。調(diào)用該方法的代碼如下:

Integer ret = Demo.<Integer> pick(new Integer(1), new Integer(2));

前文已經(jīng)提到身笤,上面的代碼可以簡(jiǎn)寫(xiě)為:

Integer ret = Demo.pick(new Integer(1), new Integer(2));

因?yàn)閖ava編譯器會(huì)根據(jù)方法內(nèi)的參數(shù)類(lèi)型推斷出該方法返回的類(lèi)型應(yīng)該為Integer豹悬,這種機(jī)制稱(chēng)為類(lèi)型推斷(Type Inference)。

那么問(wèn)題來(lái)了液荸,加入兩個(gè)輸入?yún)?shù)為不同的類(lèi)型瞻佛,應(yīng)該返回什么類(lèi)型呢?

例如:

pick("d", new ArrayList<String>());

第一個(gè)參數(shù)為String類(lèi)型娇钱,第二個(gè)參數(shù)為ArrayList類(lèi)型伤柄,java編譯器就會(huì)根據(jù)這兩個(gè)參數(shù)類(lèi)型來(lái)推斷,盡量使返回類(lèi)型為最明確的一種文搂。本例中适刀,String與ArrayList都實(shí)現(xiàn)了同樣的接口——Serializable,當(dāng)然煤蹭,他們也是Object的子類(lèi)笔喉,Serializable類(lèi)型顯然比Object類(lèi)型更加明確,因?yàn)樗姆秶「?xì)分硝皂,所以最終的返回類(lèi)型應(yīng)該為Serializable:

Serializable s = pick("d", new ArrayList<String>());

在泛型類(lèi)實(shí)例化的時(shí)候同樣可以利用這種機(jī)制簡(jiǎn)化代碼常挚,需要注意的是,尖括號(hào)“<>”在此時(shí)是不能省略的稽物。例如:

Map<String, List<String>> myMap = new HashMap<String, List<String>>();
//編譯器能推斷出后面的類(lèi)型待侵,所以可以簡(jiǎn)化為:
Map<String, List<String>> myMap = new HashMap<>();
//但是,不能簡(jiǎn)化為:
Map<String, List<String>> myMap = new HashMap();
//因?yàn)镠ashMap()是HashMap原始類(lèi)型(Raw Type)的構(gòu)造函數(shù)姨裸,而非HashMap<String, List<String>>的構(gòu)造函數(shù)秧倾,如果不加“<>”編譯器不會(huì)進(jìn)行類(lèi)型檢查

5、通配符

上文中我們提到過(guò)一個(gè)例子:

public void boxTest(Box<Number> n){
        //……
}

該方法只能接受Box<Number>這一種類(lèi)型的參數(shù)傀缩,當(dāng)我們輸入一個(gè)Box<Double>或者Box<Integer>時(shí)會(huì)報(bào)錯(cuò)那先,盡管Integer與Double是Number的子類(lèi)∩募瑁可是如果我們希望該方法可以接受Number以及它的任何子類(lèi)售淡,該怎么辦呢?

這時(shí)候就要用到通配符了慷垮,改寫(xiě)如下:

public void boxTest(Box<? extends Number> n){
        //……
}

? extends Number就代表可以接受Number以及它的子類(lèi)作為參數(shù)揖闸。這種聲明方式被稱(chēng)為上限通配符(upper bounded wildcard)。

相反地料身,如果我們希望該方法可以接受Integer汤纸,Number以及Object類(lèi)型的參數(shù)怎么辦呢?應(yīng)該使用下限通配符(lower bounded wildcard):

public void boxTest(Box<? super Integer> n){
        //……
}

? super Integer代表可以接受Integer以及它的父類(lèi)作為參數(shù)芹血。

如果類(lèi)型參數(shù)中既沒(méi)有extends 關(guān)鍵字贮泞,也沒(méi)有super關(guān)鍵字,只有一個(gè)?幔烛,代表無(wú)限定通配符(Unbounded Wildcards)啃擦。

通常在兩種情況下會(huì)使用無(wú)限定通配符:

  • 如果正在編寫(xiě)一個(gè)方法,可以使用Object類(lèi)中提供的功能來(lái)實(shí)現(xiàn)
  • 代碼實(shí)現(xiàn)的功能與類(lèi)型參數(shù)無(wú)關(guān)饿悬,比如List.clear()List.size()方法令蛉,還有經(jīng)常使用的Class<?>方法,其實(shí)現(xiàn)的功能都與類(lèi)型參數(shù)無(wú)關(guān)狡恬。

來(lái)看一個(gè)例子:

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

該方法只能接受List<Object>型的參數(shù)珠叔,不接受其他任何類(lèi)型的參數(shù)。但是傲宜,該方法實(shí)現(xiàn)的功能與List之中參數(shù)類(lèi)型沒(méi)有關(guān)系运杭,所以我們希望它可以接受包含任何類(lèi)型的List參數(shù)。代碼改動(dòng)如下:

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

需要特別注意的是函卒,List<?>與List<Object>并不相同辆憔,無(wú)論A是什么類(lèi)型,List<A>是List<?>的子類(lèi)报嵌,但是虱咧,List<A>不是List<Object>的子類(lèi)。

例如:

List<Number> lb = new ArrayList<>();
List<Integer> la = lb;   // 會(huì)報(bào)編譯錯(cuò)誤锚国,盡管Integer是Number的子類(lèi)腕巡,但是List<Integer>不是List<Number>的子類(lèi)

List<Integer>與List<Number>的關(guān)系如下:

3.png

所以,下面的代碼是正確的:

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // 不會(huì)報(bào)錯(cuò)血筑, List<? extends Integer> 是 List<? extends Number>的子類(lèi)

下面這張圖介紹了上限通配符绘沉、下限通配符煎楣、無(wú)限定通配符之間的關(guān)系:

4.png

編譯器可以通過(guò)類(lèi)型推斷機(jī)制來(lái)決定通配符的類(lèi)型,這種情況被稱(chēng)為通配符捕獲车伞。大多時(shí)候我們不必?fù)?dān)心通配符捕獲择懂,除非編譯器報(bào)出了包含“capture of”的錯(cuò)誤。例如:

    public class WildcardError {

        void foo(List<?> i) {
            i.set(0, i.get(0));  //會(huì)報(bào)編譯錯(cuò)誤
        }
    }

上例中另玖,調(diào)用List.set(int,E)方法的時(shí)候困曙,編譯器無(wú)法推斷i.get(0)是什么類(lèi)型,就會(huì)報(bào)錯(cuò)谦去。

我們可以借助一個(gè)私有的可以捕獲通配符的helper方法來(lái)解決這種錯(cuò)誤:

    public class WildcardFixed {
        void foo(List<?> i) {
            fooHelper(i);
        }


        // 該方法可以確保編譯器通過(guò)通配符捕獲來(lái)推斷出參數(shù)類(lèi)型
        private <T> void fooHelper(List<T> l) {
            l.set(0, l.get(0));
        }

    }

按照約定俗成的習(xí)慣慷丽,helper方法的命名方法為“原始方法”+“helper”,上例中鳄哭,原始方法為“foo”要糊,所以命名為“fooHelper”。

關(guān)于什么時(shí)候該使用上限通配符窃诉,什么時(shí)候該使用下限通配符杨耙,應(yīng)該遵循一下幾項(xiàng)指導(dǎo)規(guī)則。

首先將變量分為in-變量out-變量in-變量持有為當(dāng)前代碼服務(wù)的數(shù)據(jù)飘痛,out-變量持有其他地方需要使用的數(shù)據(jù)珊膜。

例如copy(src, dest)方法實(shí)現(xiàn)了從src源頭將數(shù)據(jù)復(fù)制到dest目的地的功能,那么src就是in-變量宣脉,而dest就是out-變量车柠。當(dāng)然,在一些情況下塑猖,一個(gè)變量可能既是in-變量也是out-變量竹祷。

  • in-變量使用上限通配符;
  • out-變量使用下限通配符羊苟;
  • 當(dāng)in-變量可以被Object類(lèi)中的方法訪(fǎng)問(wèn)時(shí)塑陵,使用無(wú)限定通配符;
  • 一個(gè)變量既是in-變量也是out-變量時(shí)蜡励,不使用通配符

注意令花,上面的規(guī)則不適用于方法的返回類(lèi)型。

6凉倚、類(lèi)型擦除

java編譯器在處理泛型的時(shí)候兼都,會(huì)做下面幾件事:

  • 將沒(méi)有限定的類(lèi)型參數(shù)用Object替換,保證class文件中只含有正常的類(lèi)稽寒、接口與方法扮碧;
  • 在必要的時(shí)候進(jìn)行類(lèi)型轉(zhuǎn)換,保證類(lèi)型安全;
  • 在泛型的繼承上使用橋接方法(bridge methods)保持多態(tài)性慎王。

這類(lèi)操作被稱(chēng)為類(lèi)型擦除(Type Erasure)蚓土。

例如:

    public class Node<T> {

        private T data;
        private Node<T> next;

        public Node(T data, Node<T> next) {
            this.data = data;
            this.next = next;
        }

        public T getData() {
            return data;
        }
        // ...
    }

該類(lèi)中的T沒(méi)有被extends或者super限定,會(huì)被編譯器替換成Object:

    public class Node {

        private Object data;
        private Node next;

        public Node(Object data, Node next) {
            this.data = data;
            this.next = next;
        }

        public Object getData() {
            return data;
        }
        // ...
    }

如果T加了限定柬祠,編譯器會(huì)將它替換成合適的類(lèi)型:

public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
       this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

改造成:

    public class Node {

        private Comparable data;
        private Node next;

        public Node(Comparable data, Node next) {
            this.data = data;
            this.next = next;
        }

        public Comparable getData() {
            return data;
        }
        // ...
    }

方法中的類(lèi)型擦除與之類(lèi)似北戏。

有時(shí)候類(lèi)型擦除會(huì)產(chǎn)生一些我們預(yù)想不到的情況,下面通過(guò)一個(gè)例子來(lái)分析它是如何產(chǎn)生的漫蛔。

   public class Node<T> {

        public T data;

        public Node(T data) {
            this.data = data;
        }

        public void setData(T data) {
            System.out.println("Node.setData");
            this.data = data;
        }
    }

    public class MyNode extends Node<Integer> {
        public MyNode(Integer data) {
            super(data);
        }

        public void setData(Integer data) {
            System.out.println("MyNode.setData");
            super.setData(data);
        }
    }

上面的代碼定義了兩個(gè)類(lèi),MyNode類(lèi)繼承了Node類(lèi)旧蛾,然后運(yùn)行下面的代碼:

MyNode mn = new MyNode(5);
Node n = mn;            
n.setData("Hello");     
Integer x = mn.data;    // 拋出ClassCastException異常

上面的代碼在類(lèi)型擦除之后會(huì)轉(zhuǎn)換成下面的形式:

MyNode mn = new MyNode(5);
Node n = (MyNode)mn;         

n.setData("Hello");
Integer x = (String)mn.data;   // 拋出ClassCastException異常

我們來(lái)看看代碼是怎么執(zhí)行的:

  • (1)n.setData("Hello")調(diào)用的其實(shí)是MyNode類(lèi)的setData(Object)方法(從Node類(lèi)繼承的)莽龟;
  • (2)n引用的對(duì)象中的data字段被賦值一個(gè)String變量;
  • (3)mn引用的相同對(duì)象中的data預(yù)期為Integer類(lèi)型(mn為Node<Integer>類(lèi)型)锨天;
  • (4)第四行代碼試圖將一個(gè)String賦值給Integer類(lèi)型的變量毯盈,所以引發(fā)了ClassCastException異常。

當(dāng)編譯一個(gè)繼承了帶有參數(shù)化泛型的類(lèi)或借口時(shí)病袄,編譯器會(huì)根據(jù)需要?jiǎng)?chuàng)建被稱(chēng)為bridge method的橋接方法搂赋,這是類(lèi)型擦除中的一部分。

上例中MyNode繼承了Node<Integer>類(lèi)益缠,類(lèi)型擦除之后脑奠,代碼變?yōu)椋?/p>

    class MyNode extends Node {

        //編譯器添加的橋接方法
        public void setData(Object data) {
            setData((Integer) data);
        }

        // MyNode的該方法并沒(méi)有覆寫(xiě)父類(lèi)的setData(Object data)方法,因?yàn)閰?shù)類(lèi)型不一樣
        public void setData(Integer data) {
            System.out.println("MyNode.setData");
            super.setData(data);
        }

        // ...
    }

7幅慌、注意事項(xiàng)

為了高效地使用泛型宋欺,應(yīng)該注意下面幾個(gè)方面:

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

例如

   class Pair<K, V> {

        private K key;
        private V value;

        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }

        // ...
    }

當(dāng)創(chuàng)建一個(gè)Pair類(lèi)時(shí),不能用基本類(lèi)型來(lái)替代K胰伍,V兩個(gè)類(lèi)型參數(shù)齿诞。

Pair<int, char> p = new Pair<>(8, 'a');  // 編譯錯(cuò)誤
Pair<Integer, Character> p = new Pair<>(8, 'a');  //正確寫(xiě)法

(2)不可實(shí)例化類(lèi)型參數(shù)

例如:

public static <E> void append(List<E> list) {
    E elem = new E();  // 編譯錯(cuò)誤
    list.add(elem);
}

但是,我們可以通過(guò)反射實(shí)例化帶有類(lèi)型參數(shù)的對(duì)象:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // 正確
    list.add(elem);
}

List<String> ls = new ArrayList<>();
append(ls, String.class);  //傳入類(lèi)型參數(shù)的Class對(duì)象

(3)不能在靜態(tài)字段上使用泛型

通過(guò)一個(gè)反例來(lái)說(shuō)明:

public class MobileDevice<T> {
    private static T os;  //假如我們定義了一個(gè)帶泛型的靜態(tài)字段

    // ...
}

MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();

因?yàn)殪o態(tài)變量是類(lèi)變量骂租,被所有實(shí)例共享祷杈,此時(shí),靜態(tài)變量os的真實(shí)類(lèi)型是什么呢渗饮?顯然不能同時(shí)是Smartphone但汞、Pager、TabletPC抽米。

這就是為什么不能在靜態(tài)字段上使用泛型的原因特占。

(4)不能對(duì)帶有參數(shù)化類(lèi)型的類(lèi)使用cast或instanceof方法

public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // 編譯錯(cuò)誤
        // ...
    }
}

傳給蓋該方法的參數(shù)化類(lèi)型集合為:

S = { ArrayList<Integer>, ArrayList<String> LinkedList<Character>, ... }

運(yùn)行環(huán)境并不會(huì)跟蹤類(lèi)型參數(shù),所以分辨不出ArrayList<Integer>與ArrayList<String>云茸,我們能做的至多是使用無(wú)限定通配符來(lái)驗(yàn)證list是否為ArrayList:

public static void rtti(List<?> list) {
    if (list instanceof ArrayList<?>) {  // 正確
        // ...
    }
}

同樣是目,不能將參數(shù)轉(zhuǎn)換成一個(gè)帶參數(shù)化類(lèi)型的對(duì)象,除非它的參數(shù)化類(lèi)型為無(wú)限定通配符(<?>):

List<Integer> li = new ArrayList<>();
List<Number>  ln = (List<Number>) li;  // 編譯錯(cuò)誤

當(dāng)然标捺,如果編譯器知道參數(shù)化類(lèi)型肯定有效懊纳,是允許這種轉(zhuǎn)換的:

List<String> l1 = ...;
ArrayList<String> l2 = (ArrayList<String>)l1;  // 允許轉(zhuǎn)變揉抵,類(lèi)型參數(shù)沒(méi)變化

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

例如:

List<Integer>[] arrayOfLists = new List<Integer>[2]; // 編譯錯(cuò)誤

下面通過(guò)兩段代碼來(lái)解釋為什么不行。先來(lái)看一個(gè)正常的操作:

Object[] strings = new String[2];
strings[0] = "hi";   // 插入正常
strings[1] = 100;    //報(bào)錯(cuò)嗤疯,因?yàn)?00不是String類(lèi)型

同樣的操作冤今,如果使用的是泛型數(shù)組,就會(huì)出問(wèn)題:

Object[] stringLists = new List<String>[];  // 該句代碼實(shí)際上會(huì)報(bào)錯(cuò)茂缚,但是我們先假定它可以執(zhí)行
stringLists[0] = new ArrayList<String>();   // 插入正常
stringLists[1] = new ArrayList<Integer>();  // 該句代碼應(yīng)該報(bào)ArrayStoreException的異常戏罢,但是運(yùn)行環(huán)境探測(cè)不到

(6)不能創(chuàng)建、捕獲泛型異常

泛型類(lèi)不能直接或間接繼承Throwable類(lèi)

class MathException<T> extends Exception { /* ... */ }    //編譯錯(cuò)誤

class QueueFullException<T> extends Throwable { /* ... */} // 編譯錯(cuò)誤

方法不能捕獲泛型異常:

public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
        for (J job : jobs)
            // ...
    } catch (T e) {   // 編譯錯(cuò)誤
        // ...
    }
}

但是脚囊,我們可以在throw子句中使用類(lèi)型參數(shù):

class Parser<T extends Exception> {
    public void parse(File file) throws T {     // 正確
        // ...
    }
}

(7)不能重載經(jīng)過(guò)類(lèi)型擦除后形參轉(zhuǎn)化為相同原始類(lèi)型的方法

先來(lái)看一段代碼:

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());

打印結(jié)果可能與我們猜測(cè)的不一樣龟糕,打印出的是true,而非false悔耘,因?yàn)橐粋€(gè)泛型類(lèi)的所有實(shí)例在運(yùn)行時(shí)具有相同的運(yùn)行時(shí)類(lèi)(class)讲岁,而不管他們的實(shí)際類(lèi)型參數(shù)。

事實(shí)上衬以,泛型之所以叫泛型缓艳,就是因?yàn)樗鼘?duì)所有其可能的類(lèi)型參數(shù),有同樣的行為看峻;同樣的類(lèi)可以被當(dāng)作許多不同的類(lèi)型阶淘。

認(rèn)識(shí)到了這一點(diǎn),再來(lái)看下面的例子:

public class Example { 
    public void print(Set<String> strSet) { }  //編譯錯(cuò)誤
    public void print(Set<Integer> intSet) { }  //編譯錯(cuò)誤
}

因?yàn)镾et<String>與Set<Integer>本質(zhì)上屬于同一個(gè)運(yùn)行時(shí)類(lèi)备籽,在經(jīng)過(guò)類(lèi)型擦出以后舶治,上面的兩個(gè)方法會(huì)共享一個(gè)方法簽名,相當(dāng)于一個(gè)方法车猬,所以重載出錯(cuò)霉猛。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市珠闰,隨后出現(xiàn)的幾起案子惜浅,更是在濱河造成了極大的恐慌嚣鄙,老刑警劉巖哆致,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诱桂,死亡現(xiàn)場(chǎng)離奇詭異赖欣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)兜蠕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)注整,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)串前,“玉大人军熏,你說(shuō)我怎么就攤上這事轩猩。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵均践,是天一觀的道長(zhǎng)晤锹。 經(jīng)常有香客問(wèn)我,道長(zhǎng)彤委,這世上最難降的妖魔是什么鞭铆? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮焦影,結(jié)果婚禮上车遂,老公的妹妹穿的比我還像新娘。我一直安慰自己斯辰,他們只是感情好艰额,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著椒涯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪回梧。 梳的紋絲不亂的頭發(fā)上废岂,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音狱意,去河邊找鬼湖苞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛详囤,可吹牛的內(nèi)容都是我干的财骨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼藏姐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼隆箩!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起羔杨,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捌臊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后兜材,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體理澎,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年曙寡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了糠爬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡举庶,死狀恐怖执隧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤殴玛,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布捅膘,位于F島的核電站,受9級(jí)特大地震影響滚粟,放射性物質(zhì)發(fā)生泄漏寻仗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一凡壤、第九天 我趴在偏房一處隱蔽的房頂上張望署尤。 院中可真熱鬧,春花似錦亚侠、人聲如沸曹体。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)箕别。三九已至,卻和暖如春滞谢,著一層夾襖步出監(jiān)牢的瞬間串稀,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工狮杨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留母截,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓橄教,卻偏偏與公主長(zhǎng)得像清寇,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子护蝶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載: https://blog.csdn.net/s10461/article/details/53941091...
    DaneYang閱讀 490評(píng)論 1 6
  • 泛型 體驗(yàn)泛型 沒(méi)有使用泛型時(shí)华烟,只要是對(duì)象,不管是什么類(lèi)型的對(duì)象滓走,都可以存儲(chǔ)進(jìn)同一個(gè)集合中垦江。使用泛型集合,可以將一...
    bo客先生閱讀 731評(píng)論 0 8
  • 泛型的定義及使用 1. 定義泛型: 2. 類(lèi)中使用泛型 3. 使用泛型類(lèi) 4. 使用泛型的優(yōu)勢(shì)搅方? 多泛型變量的定義...
    xue57233閱讀 435評(píng)論 0 1
  • 一:什么是泛型 泛型在我們的代碼中使用非常廣泛的一部分知識(shí)比吭,今天就系統(tǒng)的把泛型總結(jié)一下,并記錄自己的學(xué)習(xí)歷程...
    蓉漂里的小白閱讀 598評(píng)論 0 2
  • 泛型類(lèi)和泛型方法 泛型是Java語(yǔ)言中實(shí)現(xiàn)程序多態(tài)的一種重要方法姨涡,泛型多用于底層代碼中衩藤,以此來(lái)保證代碼的通用型。今...
    寫(xiě)程序的小火箭閱讀 252評(píng)論 0 0