Java 泛型

Java 泛型是 Java 5 引入的一個(gè)重要特性,相信大多數(shù) Java 開(kāi)發(fā)者都對(duì)此不陌生,但是泛型背后的實(shí)現(xiàn)原理和類(lèi)型擦除還是許多人依然不是很清楚沧卢。本文將介紹 Java 泛型的原理和使用讥脐,重點(diǎn)闡述容易產(chǎn)生困惑的通配符、類(lèi)型擦除等問(wèn)題幔戏。

1. Java 泛型

1.1 Java 泛型是什么玛追?

Java 泛型,提供了參數(shù)化類(lèi)型,并且提供了編譯時(shí)強(qiáng)類(lèi)型檢查痊剖。泛型可以讓我們很簡(jiǎn)單地支持不同類(lèi)型韩玩,在 Java 集合框架中泛型廣泛用以對(duì)類(lèi)型的抽象。

1.2 Java 泛型的好處

  • 提供編譯時(shí)的強(qiáng)類(lèi)型檢查陆馁≌彝牵可以在編譯時(shí)發(fā)現(xiàn)類(lèi)型安全問(wèn)題,不用等到運(yùn)行時(shí)叮贩。

  • 避免類(lèi)型轉(zhuǎn)換击狮。

看下面一段代碼:

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // type cast to String

如果使用泛型的話,不需要類(lèi)型轉(zhuǎn)換:

List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);   // no cast
  • 可以實(shí)現(xiàn)通用的算法益老。通用算法可以處理不同類(lèi)型的集合彪蓬,可以進(jìn)行自定義,并且類(lèi)型安全且易于閱讀杨箭。

2. 泛型類(lèi)型與泛型方法

2.1 泛型類(lèi)型

泛型類(lèi)型是指泛型類(lèi)或泛型接口寞焙。為了理解泛型類(lèi)型的概念,看下面這個(gè)例子互婿。

先定義一個(gè)簡(jiǎn)單的 Box 類(lèi):

public class Box {
    private String object;

    public void set(String object) { this.object = object; }
    public String get() { return object; }
}

上面代碼中的 Box 只能存放 String 類(lèi)型的元素捣郊,如果想存放 Integer 等其他類(lèi)型的元素,則必須重寫(xiě)另外一個(gè) Box慈参,代碼不能復(fù)用呛牲。下面再看使用泛型后的 Box:

public class Box<T> {
    // T stands for "Type"
    private T t;

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

現(xiàn)在 Box 可以存放除基本類(lèi)型外的任何類(lèi)型了。使用 T 類(lèi)型代替 String 類(lèi)型驮配,按照慣例娘扩,類(lèi)型參數(shù)名是一個(gè)大寫(xiě)字母,常見(jiàn)的類(lèi)型參數(shù)名如下:

  • E - Element(在 Java 集合框架中廣泛運(yùn)用)

  • K - Key

  • N - Number

  • T - Type

  • V - Value

2.2 泛型類(lèi)型的原始類(lèi)型(Raw Types)

原始類(lèi)型(Raw Types)是沒(méi)有指定參數(shù)類(lèi)型的泛型類(lèi)或泛型接口壮锻。例如琐旁,對(duì)于上面提到的Box<T>泛型類(lèi):

Box rawBox = new Box();

Box就是Box<T>的原始類(lèi)型,原始類(lèi)型一般出現(xiàn)在舊版代碼中猜绣,因?yàn)榇罅康?API 在 Java 5 之前不是通用的灰殴。原始類(lèi)型和泛型類(lèi)型也可以轉(zhuǎn)換:

// generic type to raw type
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;               // OK
rawBox.set(8);  // warning: unchecked invocation to set(T)

// raw type to generic type
Box rawBox = new Box();           // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox;     // warning: unchecked conversion

2.3 泛型方法

泛型方法是指有它們自己參數(shù)化類(lèi)型的方法。類(lèi)型參數(shù)在一對(duì)尖括號(hào)之間掰邢,并且在方法返回類(lèi)型之前牺陶。

下面 Util 類(lèi)有一個(gè)泛型方法:

public class Util {
    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());
    }
}

public class Pair<K, V> {

    private K key;
    private V value;

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

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

通常調(diào)用泛型方法的方式如下:

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);

// 如果在 Java 7 以上版本,利用類(lèi)型推斷可以簡(jiǎn)寫(xiě)為
boolean same = Util.compare(p1, p2);

3. 有界類(lèi)型參數(shù) (Bounded Type Parameters)

很多時(shí)候我們都想限制參數(shù)類(lèi)型的邊界辣之,例如在對(duì)比兩個(gè)對(duì)象的方法中掰伸,想確保方法參數(shù)都是 Comparable 的。聲明有界類(lèi)型參數(shù)(Bounded Type Parameters
)怀估,格式為T extends Class & Interface1 & ... & InterfaceN狮鸭。

public static <T extends Comparable<T>> int compare(T t1, T t2){
    return t1.compareTo(t2);
}

這樣當(dāng)我們傳遞的參數(shù)沒(méi)有實(shí)現(xiàn) Comparable 接口,會(huì)有編譯時(shí)錯(cuò)誤。有界類(lèi)型參數(shù)同樣可以用于泛型類(lèi)和泛型接口中怕篷,而且支持多個(gè)邊界历筝,例如 <T extends A & B & C>酗昼,只允許最多一個(gè) Class 邊界廊谓,而且如果有一個(gè) Class 邊界竿裂,Class 邊界必須在最前面险污。

4. 通配符

Java 泛型中問(wèn)號(hào)?是通配符,表示未知類(lèi)型虫碉。通配符可以用于參數(shù)呛哟、屬性叠荠、局部變量或返回值的類(lèi)型,但是不能用于泛型方法調(diào)用或創(chuàng)建泛型類(lèi)實(shí)例的類(lèi)型參數(shù)扫责。

4.1 無(wú)界通配符 (Unbounded Wildcards)

單獨(dú)使用?表示無(wú)界通配符榛鼎,例如List<?>,表示未知類(lèi)型的 list鳖孤。下面兩個(gè)場(chǎng)景適合使用無(wú)界通配符:

  • 如果想寫(xiě)一個(gè)方法者娱,只用到 Object 類(lèi)中的功能,即用List<?>代替List<Object>苏揣。

  • 當(dāng)使用到的泛型類(lèi)型的方法不依賴(lài)參數(shù)類(lèi)型時(shí)黄鳍,例如只用到 List.size 或 List.clear 方法。事實(shí)上平匈,Class<?>非常常見(jiàn)也是因?yàn)?code>Class<T>中的大多數(shù)方法都不依賴(lài) T框沟。

4.2 上限通配符 (Upper Bounded Wildcards)

上限通配符可以放寬對(duì)變量的限制。語(yǔ)法為? extends SuperType增炭,SuperType 可以是類(lèi)或接口忍燥。例如,如果想寫(xiě)一個(gè)對(duì) List<Integer>, List<Double>, List<Number>都適用的方法隙姿,可以用 List<? extends Number>

public static double sumOfList(List<? extends Number> list) {
    double s = 0.0;
    for (Number n : list)
        s += n.doubleValue();
    return s;
}

4.3 下限通配符 (Lower Bounded Wildcards)

下限通配符可以限制為特定類(lèi)型或該類(lèi)型的父類(lèi)型梅垄。語(yǔ)法為? super SubType。例如孟辑,想寫(xiě)一個(gè)方法添加 Integer 對(duì)象到 list 中哎甲,可以是 List<Integer>, List<Number>List<Object>,可以用List<? super Integer>

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

4.4 泛型的繼承與子類(lèi)型

泛型有個(gè)常見(jiàn)的誤解:Integer 是 Number 的子類(lèi)型饲嗽,所以 Box<Integer> 也是 Box<Number> 的子類(lèi)型炭玫。但是其實(shí)Box<Integer>Box<Number>并沒(méi)有直接關(guān)系。

public void boxTest(Box<Number> n) { /* ... */ }
// 如果傳 Box<Integer> 會(huì)出現(xiàn)編譯錯(cuò)誤
generics-1.gif

泛型類(lèi)的繼承貌虾,可以看 Collection 的關(guān)系吞加,ArrayList<String>List<String>的子類(lèi)型:

generics-2.gif

上面提到Box<Integer>Box<Number>都是 Object 子類(lèi),但是它們還有個(gè)共有的父類(lèi)型Box<?>,同理List<Number>List<Integer>的父類(lèi)型為List<?>衔憨。

generics-3.gif

至于上限通配符和下限通配符間的關(guān)系叶圃,見(jiàn)下圖:

generics-4.gif

4.5 通配符捕獲 (Wildcard Capture)

有些時(shí)候,編譯器會(huì)推測(cè)通配符的類(lèi)型践图,例如List<?>類(lèi)型掺冠,在某些代碼中編譯器從代碼推斷出具體的類(lèi)型,這種場(chǎng)景就是通配符捕獲码党。大多數(shù)情況下德崭,我們都不需要關(guān)心通配符捕獲,除非看到錯(cuò)誤信息中包含“CAP#”揖盘。

下面代碼會(huì)產(chǎn)生捕獲錯(cuò)誤:

public class WildcardError {
    void foo(List<?> i) {
        i.set(0, i.get(0)); // 錯(cuò)誤: 不兼容的類(lèi)型: Object無(wú)法轉(zhuǎn)換為CAP#1
    }
}

上面代碼中的錯(cuò)誤一開(kāi)始可能覺(jué)得莫名其妙眉厨,先看看錯(cuò)誤信息:i.set(int, capture<?>) 需要的參數(shù)類(lèi)型為int,CAP#1,而實(shí)際傳入的為int,Object兽狭,編譯器將 i.get(0) 返回的類(lèi)型推斷為 Object憾股。當(dāng)調(diào)用List.set(int, E)時(shí),編譯器無(wú)法確認(rèn)傳入的類(lèi)型與 List 的元素類(lèi)型一致箕慧,雖然我們?nèi)藶橹肋@處調(diào)用的類(lèi)型是一致的服球。

我們可以額外加一個(gè)泛型方法來(lái)避免編譯錯(cuò)誤:

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

    // Helper method created so that the wildcard can be captured
    // through type inference.
    private <T> void fooHelper(List<T> l) {
        l.set(0, l.get(0)); // 傳入?yún)?shù)也為 T,編譯器推斷為 CAP#1
    }
}

下面再看一個(gè)更復(fù)雜的例子:

public class WildcardErrorBad {
    void swapFirst(List<? extends Number> l1, List<? extends Number> l2) {
      Number temp = l1.get(0);
      l1.set(0, l2.get(0)); // 錯(cuò)誤: 不兼容的類(lèi)型: Number無(wú)法轉(zhuǎn)換為CAP#1
      l2.set(0, temp);      // 錯(cuò)誤: 不兼容的類(lèi)型: Number無(wú)法轉(zhuǎn)換為CAP#1
    }
}

這個(gè)例子試圖執(zhí)行一個(gè)不安全的操作销钝,看下面調(diào)用的場(chǎng)景:

List<Integer> li = Arrays.asList(1, 2, 3);
List<Double>  ld = Arrays.asList(10.10, 20.20, 30.30);
swapFirst(li, ld);

雖然List<Integer>List<Double>都符合List<? extends Number>類(lèi)型有咨,但是List<Integer>列表中存放 Double 類(lèi)型的元素顯然不正確,所以也無(wú)法添加其他泛型方法來(lái)解決這個(gè)問(wèn)題蒸健。

4.6 PECS 原則

在學(xué)習(xí)泛型的過(guò)程座享,一個(gè)容易困惑的問(wèn)題是如何什么時(shí)候用上限通配符或下限通配符。下面先分析兩者的具體使用區(qū)別似忧。

對(duì)于List<? extends Number>類(lèi)型渣叛,可以執(zhí)行哪些操作呢?

List<? extends Number> list = new ArrayList<Integer>();

Number first = list.get(0); // OK
list.add(null); // OK
Number number = 1;
list.add(number); // 錯(cuò)誤: 不兼容的類(lèi)型: Number無(wú)法轉(zhuǎn)換為CAP#1
list.clear(); // OK
list.remove(0); // OK

List<? extends Number>類(lèi)型可以添加 null 值盯捌,也可以通過(guò)泛型方法寫(xiě)入從本身 list 讀取的值淳衙,但是無(wú)法添加新的元素。無(wú)法添加新元素的原因饺著,是對(duì)于List<? extends Number>類(lèi)型來(lái)說(shuō)箫攀,可能是 List<Number>List<Integer>List<Double>等類(lèi)型幼衰,無(wú)法確定新元素的類(lèi)型與集合里的類(lèi)型一致靴跛,所以編譯器會(huì)提示報(bào)錯(cuò)。所以可以將List<? extends Number>類(lèi)型的列表看作非嚴(yán)格意義上的只讀列表渡嚣。

而對(duì)于List<? super Number>類(lèi)型呢:

List<? super Number> list = new ArrayList<Object>();

Number first = list.get(0); // 錯(cuò)誤: 不兼容的類(lèi)型: CAP#1無(wú)法轉(zhuǎn)換為Number
list.add(null); // OK
Number number = 1;
list.add(number); // OK
list.clear(); // OK
list.remove(0); // OK

List<? super Number>類(lèi)型可以添加 null 值梢睛、添加新的元素肥印,也可以刪除元素,但是無(wú)法讀取列表中的值绝葡。無(wú)法讀取列表的原因深碱,是對(duì)于List<? super Number>類(lèi)型來(lái)說(shuō),可能是List<Number>也可能是List<Object>類(lèi)型藏畅,讀取列表元素時(shí)不能確定元素類(lèi)型敷硅。所以可以將List<? super Number>類(lèi)型的列表看作只寫(xiě)列表。

上面兩個(gè)例子中墓赴,只讀類(lèi)型相當(dāng)于生產(chǎn)者(Producer)竞膳,生產(chǎn) T,就使用? extends T诫硕,只寫(xiě)類(lèi)型相當(dāng)于消費(fèi)者(Consumer),消費(fèi) T刊侯,就使用? super T章办。也就是“Producer Extends, Consumer Super”,簡(jiǎn)稱(chēng) PECS 原則滨彻。

Collections.copy 方法就用到了這個(gè)原則藕届,copy(List<? super T> dest, List<? extends T> src),src 列表是只讀的亭饵,dest 列表是只寫(xiě)的休偶。

通配符的使用建議如下:

  • 只讀類(lèi)型使用上限通配符? extends T

  • 只寫(xiě)類(lèi)型使用下限通配符? super T

  • 如果只讀類(lèi)型只用到 Object 的方法,即List<? extends Object>辜羊,可以用List<?>無(wú)界通配符

  • 對(duì)于同時(shí)需要讀取和寫(xiě)入的類(lèi)型踏兜,不要使用通配符

上面四條建議都不適用于方法返回值類(lèi)型。應(yīng)該避免在返回值中使用通配符八秃,因?yàn)檫@樣會(huì)強(qiáng)制要求調(diào)用者調(diào)用時(shí)處理通配符碱妆。

5. 類(lèi)型擦除

類(lèi)型擦除是 Java 泛型中最容易產(chǎn)生困惑的地方,舉個(gè)很簡(jiǎn)單的例子昔驱,許多人誤以為List<String>List<Integer>的 Class 類(lèi)型不一致:

List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
System.out.println(strList.getClass().getName());   // java.util.ArrayList
System.out.println(intList.getClass().getName());   // java.util.ArrayList
System.out.println(strList.getClass() == intList.getClass());   // true

在編譯時(shí)List<String>List<Integer>的類(lèi)型是不一樣的疹尾,但是在運(yùn)行時(shí)兩者的類(lèi)型又是一樣的,背后的原因就是類(lèi)型擦除骤肛。

Java 泛型添加是為了提供編譯時(shí)的類(lèi)型檢查和支持泛型編程纳本,并沒(méi)有運(yùn)行時(shí)的支持。所以 Java 編譯器會(huì)用類(lèi)型擦除來(lái)刪除所有泛型類(lèi)型檢查代碼腋颠,并在必要時(shí)插入強(qiáng)制類(lèi)型轉(zhuǎn)換繁成。類(lèi)型擦除確保不為參數(shù)化類(lèi)型創(chuàng)建新類(lèi),所以ArrayList<E>的 Class 類(lèi)型還是java.util.ArrayList秕豫,相應(yīng)的朴艰,泛型也不會(huì)增加運(yùn)行時(shí)開(kāi)銷(xiāo)观蓄。Java 編譯器在應(yīng)用泛型類(lèi)型擦除時(shí)有以下行為:

  • 將泛型中所有參數(shù)化類(lèi)型替換為泛型邊界,如果參數(shù)化類(lèi)型是無(wú)界的祠墅,則替換為 Object 類(lèi)型侮穿。字節(jié)碼中沒(méi)有任何泛型的相關(guān)信息。

  • 為了類(lèi)型安全毁嗦,在必要時(shí)插入類(lèi)型轉(zhuǎn)換代碼亲茅。

  • 生成橋接方法來(lái)保持泛型類(lèi)型的多態(tài)性。

5.1 參數(shù)化類(lèi)型替換

對(duì)于無(wú)解參數(shù)化類(lèi)型狗准,類(lèi)型擦除時(shí)會(huì)替換為 Object克锣。

下面看單鏈表中節(jié)點(diǎn)類(lèi):

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; }
    // ...
}

經(jīng)過(guò)類(lèi)型擦除后:

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; }
    // ...
}

對(duì)于有界參數(shù)化類(lèi)型,類(lèi)型擦除時(shí)會(huì)替換為第一個(gè)邊界腔长。

如果節(jié)點(diǎn)類(lèi)使用有界參數(shù)化類(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; }
    // ...
}

經(jīng)過(guò)類(lèi)型擦除后:

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; }
    // ...
}

5.2 類(lèi)型轉(zhuǎn)換

經(jīng)過(guò)參數(shù)化類(lèi)型替換后袭祟,在使用泛型相關(guān)內(nèi)容時(shí),通常需要添加類(lèi)型轉(zhuǎn)換代碼捞附,看下面代碼:

Node<String> node = new Node<>("Hello", null);
String data = node.getData();   // 實(shí)際上 node.getData() 返回的是 Object 類(lèi)型

所以編譯器還會(huì)插入類(lèi)型轉(zhuǎn)換代碼巾乳,編譯后如下:

Node node = new Node("Hello", null);
String data = (String) node.getData();

5.3 橋接方法

當(dāng)編譯一個(gè)類(lèi)繼承泛型類(lèi)或泛型接口,在類(lèi)型擦除的過(guò)程中編譯器會(huì)生成一個(gè)合成方法鸟召,也稱(chēng)為橋接方法胆绊。

看下面代碼:

interface Comparable <A> { 
    public int compareTo( A that); 
} 
final class NumericValue implements Comparable <NumericValue> { 
    priva te byte value;  
    public  NumericValue (byte value) { this.value = value; } 
    public  byte getValue() { return value; }  
    public  int compareTo( NumericValue t hat) { return this.value - that.value; } 
}

經(jīng)過(guò)參數(shù)化類(lèi)型替換后,Comparable 接口的 compareTo 方法的參數(shù)類(lèi)型為 Object欧募,而 NumericValue 也需要實(shí)現(xiàn)compareTo(Object)方法压状,經(jīng)過(guò)類(lèi)型擦除后:

interface Comparable { 
    public int compareTo( Object that); 
} 
final class NumericValue implements Comparable { 
    priva te byte value;  
    public  NumericValue (byte value) { this.value = value; }  
    public  byte getValue() { return value; }  
    public  int compareTo( NumericValue t hat)   { return this.value - that.value; } 

    // 新合成的橋接方法
    public  int compareTo(Object that) { return this.compareTo((NumericValue)that);  } 
}

類(lèi)型擦除后NumericValue.compareTo(NumericValue)方法不再是接口的實(shí)現(xiàn)方法,這是類(lèi)型擦除的一個(gè)副作用:兩個(gè)方法(在接口和實(shí)現(xiàn)類(lèi)中)在類(lèi)型擦除之前具有相同的簽名跟继,而在類(lèi)型擦除之后具有不同的簽名种冬。

為了讓 NumericValue 依然正確地實(shí)現(xiàn) Comparable 接口,編譯器添加了一個(gè)橋接方法还栓,和接口的簽名相同碌廓,橋接方法委托給實(shí)現(xiàn)類(lèi)中的原始方法。

雖然存在橋接方法剩盒,但是一般情況下谷婆,編譯器不允許我們調(diào)用橋接方法:

NumericValue value = new NumericValue((byte) 0);
value.compareTo(value); // OK
value.compareTo("abc"); // error

但是,還有兩種方式可以調(diào)用橋接方法:使用原始類(lèi)型(Raw Types)或反射辽聊。但是橋接方法中有類(lèi)型轉(zhuǎn)換纪挎,所以傳其他類(lèi)型會(huì)有運(yùn)行時(shí)報(bào)錯(cuò)。下面是使用原始類(lèi)型的例子:

Comparable comparable = new NumericValue((byte) 0);
comparable.compareTo(comparable); // OK
comparable.compareTo("abc");    // OK at compile time, throws ClassCastException at run time

6. 泛型的限制

6.1 不能用基本類(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;
    }
    // ...
}

Pair<int, char> p = new Pair<>(8, 'a');  // compile-time error

Pair<Integer, Character> p = new Pair<>(8, 'a'); // ok跟匆,because of autoboxing

6.2 不能創(chuàng)建參數(shù)化類(lèi)型的實(shí)例

不能創(chuàng)建參數(shù)化類(lèi)型的實(shí)例异袄,但是可以用反射創(chuàng)建:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E e1 = new E(); // error
    E e2 = cls.newInstance(); // ok
    list.add(e2);
}

6.3 不能將靜態(tài)屬性聲明為泛型類(lèi)型

類(lèi)的靜態(tài)屬性是類(lèi)級(jí)別的屬性,被該類(lèi)所有實(shí)例共享玛臂,所以不允許靜態(tài)屬性是參數(shù)化類(lèi)型:

public class MobileDevice<T> {
    private static T os; // compile-time error, if has MobileDevice<Phone> and MobileDevice<Pc> instance, can not confirm the type of os.
}

6.4 不能對(duì)參數(shù)化類(lèi)型使用 Casts 或 instanceof

不能 Casts 為參數(shù)化類(lèi)型烤蜕,除非是無(wú)界通配符類(lèi)型:

List<Integer> li = new ArrayList<>();
List<?> list = li;
List<Number> ln = (List<Number>) li;    // compile-time error

但是有些場(chǎng)景封孙,編譯器知道參數(shù)化類(lèi)型是合法的,也會(huì)運(yùn)行類(lèi)型轉(zhuǎn)換:

List<String> l1 = new ArrayList<>();
ArrayList<String> l2 = (ArrayList<String>)l1;  // OK

因?yàn)轭?lèi)型擦除讽营,無(wú)法確定運(yùn)行時(shí)參數(shù)化類(lèi)型具體是什么類(lèi)型虎忌,所以無(wú)法使用 instanceof 校驗(yàn)類(lèi)型:

public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // compile-time error
        // ...
    }
    if (list instanceof ArrayList<?>) {  // OK; instanceof requires a reifiable type
        // ...
    }
}

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

ArrayList<String>[] arrayOfList = new ArrayList<String>[3]; // compile-time error

6.6 不能創(chuàng)建、捕捉或拋出參數(shù)化類(lèi)型的對(duì)象

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

// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ }    // compile-time error

// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ } // compile-time error

也無(wú)法捕捉參數(shù)化類(lèi)型的異常:

public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
        for (J job : jobs)
            // ...
    } catch (T e) {   // compile-time error
        // ...
    }
}

但是橱鹏,可以在 throws 語(yǔ)句中使用參數(shù)化類(lèi)型:

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

6.7 不能重載參數(shù)類(lèi)型為相同原始類(lèi)型的方法

不能有兩個(gè)重載方法膜蠢,當(dāng)他們的方法簽名在類(lèi)型擦除后是一樣的。

public class Example {
    public void print(List<String> list) {}
    public void print(List<Integer> list) {}
}

推薦閱讀:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末莉兰,一起剝皮案震驚了整個(gè)濱河市挑围,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌糖荒,老刑警劉巖杉辙,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異寂嘉,居然都是意外死亡奏瞬,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)泉孩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人并淋,你說(shuō)我怎么就攤上這事寓搬。” “怎么了县耽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵句喷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我兔毙,道長(zhǎng)唾琼,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任澎剥,我火速辦了婚禮锡溯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘哑姚。我一直安慰自己祭饭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布叙量。 她就那樣靜靜地躺著倡蝙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绞佩。 梳的紋絲不亂的頭發(fā)上寺鸥,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天猪钮,我揣著相機(jī)與錄音,去河邊找鬼胆建。 笑死烤低,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的眼坏。 我是一名探鬼主播拂玻,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼宰译!你這毒婦竟也來(lái)了檐蚜?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤沿侈,失蹤者是張志新(化名)和其女友劉穎闯第,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體缀拭,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咳短,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛛淋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咙好。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖褐荷,靈堂內(nèi)的尸體忽然破棺而出勾效,到底是詐尸還是另有隱情,我是刑警寧澤叛甫,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布层宫,位于F島的核電站,受9級(jí)特大地震影響其监,放射性物質(zhì)發(fā)生泄漏萌腿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一抖苦、第九天 我趴在偏房一處隱蔽的房頂上張望毁菱。 院中可真熱鬧,春花似錦睛约、人聲如沸鼎俘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)贸伐。三九已至,卻和暖如春怔揩,著一層夾襖步出監(jiān)牢的瞬間捉邢,已是汗流浹背脯丝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伏伐,地道東北人宠进。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像藐翎,于是被迫代替她去往敵國(guó)和親材蹬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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

  • 開(kāi)發(fā)人員在使用泛型的時(shí)候吝镣,很容易根據(jù)自己的直覺(jué)而犯一些錯(cuò)誤堤器。比如一個(gè)方法如果接收List作為形式參數(shù),那么如果嘗試...
    時(shí)待吾閱讀 1,055評(píng)論 0 3
  • 參數(shù)類(lèi)型的好處 在 Java 引入泛型之前末贾,泛型程序設(shè)計(jì)是用繼承實(shí)現(xiàn)的闸溃。ArrayList 類(lèi)只維護(hù)一個(gè) Obje...
    杰哥長(zhǎng)得帥閱讀 880評(píng)論 0 3
  • 一、基本概念和用法 在Java語(yǔ)言處于還沒(méi)有出現(xiàn)泛型的版本時(shí)集索,只能通過(guò)Object是所有類(lèi)型的父類(lèi)和類(lèi)型強(qiáng)制轉(zhuǎn)換兩...
    developerzjy閱讀 4,420評(píng)論 3 47
  • ??在Effective中講到泛型之處提到了一個(gè)概念寸齐,類(lèi)型擦除器,這是什么呢抄谐?接下來(lái)我們跟隨這篇文章探索類(lèi)型擦除的...
    凌云_00閱讀 2,140評(píng)論 0 8
  • 泛型,一個(gè)孤獨(dú)的守門(mén)者扰法。 大家可能會(huì)有疑問(wèn)蛹含,我為什么叫做泛型是一個(gè)守門(mén)者。這其實(shí)是我個(gè)人的看法而已塞颁,我的意思是說(shuō)泛...
    jackcooper閱讀 442評(píng)論 2 2