Java高級特性-泛型

 

RoadMap

1. 什麼是泛型

 

泛型是一種參數化類型的機制。它可使得代碼適用於各類類型,從而編寫更加通用的代碼,例如集合框架。java

泛型是一種編譯時類型確認機制。它提供了編譯期的類型安全程序員

2. 泛型的優點

1,類型安全。 泛型的主要目標是提升 Java 程序的類型安全。經過知道使用泛型定義的變量的類型限制,編譯器能夠在一個高得多的程度上驗證類型假設。沒有泛型,這些假設就只存在於程序員的頭腦中(或者若是幸運的話,還存在於代碼註釋中)。數組

2,消除強制類型轉換。 泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,而且減小了出錯機會。安全

3,潛在的性能收益。 泛型爲較大的優化帶來可能。在泛型的初始實現中,編譯器將強制類型轉換(沒有泛型的話,程序員會指定這些強制類型轉換)插入生成的字節碼中。可是更多類型信息可用於編譯器這一事實,爲將來版本的 JVM 的優化帶來可能。因爲泛型的實現方式,支持泛型(幾乎)不須要 JVM 或類文件更改。全部工做都在編譯器中完成,編譯器生成相似於沒有泛型(和強制類型轉換)時所寫的代碼,只是更能確保類型安全而已。框架

3. 泛型類

定義泛型類 在 類名後面 加上<T> 表示這是個泛型類ide

public class Crate<T> {
    private T contents;
    public T emptyCrate() {
        return contents;
    }
    public void packCrate(T contents) {
        this.contents = contents;
    }
}

這個泛型類型,能夠在類內任何地方出現,函數

如 屬性類型,方法的返回值,方法的參數類型。性能

在生成實例的時候 必須指定具體類型。優化

// create an instance with generic type
Crate<Elephant> crateForElephant = new Crate<>();

泛型數量能夠是多個this

public class SizeLimitedCrate<T, U> {
    private T contents;
    private U sizeLimit;
    public SizeLimitedCrate(T contents, U sizeLimit) {
        this.contents = contents;
        this.sizeLimit = sizeLimit;
    } 
}



// create an instance with generic types
SizeLimitedCrate<Elephant, Integer> c1 = new SizeLimitedCrate<>()

泛型類命名規範
理論上來講,泛型的類型名字能夠定義成任何你想要的。爲了方便起見,提升可讀性,

JDK建議你們採用 單個大寫字母,區分泛型與真實類名,同時提供了一些經常使用的建議泛型
E  表示一個元素
K  表示一個鍵值對的鍵
V 表示一個鍵值對的值
N 表示一個數字
T 表示一個通用類型
若是是多個通用類型,能夠延續使用,S, U, V, .

3.1 多態下的泛型類

泛型類支持接口定義, 即定義一個泛型接口。

public interface Shippable<T> {
    void ship(T t);
}

那麼問題來了,這個泛型類怎麼去實現?有三種方式能夠實現

3.1.1 指定具體的泛型類型

在實現接口的同時 指定具體的類型 而不用泛型表示。

class ShippableRobotCrate implements Shippable<Robot> {
    public void ship(Robot t) { }
}

3.1.2 繼續泛化類型

在實現接口的同時,本身也變成泛化類,進一步,泛化下沉。

class ShippableAbstractCrate<U> implements Shippable<U> {
    public void ship(U t) { }
}

3.1.3 沒有泛型的實現

    在實現接口的同時,不繼續使用泛型,取而代之的是Object類型,這是個古老方法,主要是爲了向前兼容,對那些沒有泛型支持的兼容。

    對於編譯器而言,會拋出警告,可是會經過編譯。

class ShippableCrate implements Shippable {
    public void ship(Object t) { }
}

3.2 泛型類型參數的約束

1. 構造函數不能泛型, 如new T() 最終變成 new Object()
2. 不能使用靜態類型的數組
3. 不能使用instanceof, 運行的時候泛型會被擦除
4. 不能使用基本類型做爲泛型的參數,能夠經過封裝類如:Integer
5. 不能使用靜態類型做爲參數

4. 泛型方法

4.1 泛型方法的聲明

泛型方法 與 泛型類有點相似,只是它做用與具體的方法,範圍相對於泛型類 更小。

在定義方法的時候 在聲明返回值的前面 使用<T> 來聲明泛型方法。

public static <T> void sink(T t) { }

對於方法的返回類型,也能夠是泛型或者是泛型類。

// 返回一個泛型
public static <T> T identity(T t) { return t; }


// 返回一個泛型類
public static <T> Crate<T> ship(T t) {
    System.out.println("Preparing " + t);
    return new Crate<T>();
}

 

一樣的, 泛型方法支持多個 泛型類型

// 返回一個泛型
public static <T,U> T identity(T t,U u) { return t; }

4.2 泛型方法的調用

4.2.1 顯示調用

調用具體的泛型方法時,須要指定具體類型

Box.<String>ship("package");
Box.<String[]>ship(args);

4.2.2 隱式調用

調用泛型方法的時候能夠向正常的方法調用同樣, java編譯器會自動匹配泛型

Box.ship("package");

 

5 泛型擦除

泛型的出現幫助編譯器可以在編譯的時候,使用正確的類型。

實際上,編譯器 是將全部的泛型替換爲 Object,換句話說,代碼編譯以後,這些泛型都將被Object所取代。這麼作的目的主要是爲了兼容老版本的代碼(非泛型)

public class Crate {
    private Object contents;
    public Object emptyCrate() {
        return contents;
    }
    public void packCrate(Object contents) {
        this.contents = contents;
    }
}

也不用過於擔憂 這個泛型擦除,編譯期會自動轉型了那些被擦除了泛型 如:
當你調用方法: Robot r = crate.emptyCrate();
編譯期 實際會編譯出顯示轉型的代碼
Robot r = (Robot) crate.emptyCrate();

6 與老代碼合做

class Dragon {}
class Unicorn { }
    public class LegacyDragons {
    public static void main(String[] args) {
        List unicorns = new ArrayList();
        unicorns.add(new Unicorn());
        printDragons(unicorns);
    }
    private static void printDragons(List<Dragon> dragons) {
        for (Dragon dragon: dragons) { // ClassCastException
            System.out.println(dragon);
        } 
    } 
}

    雖然有了泛型擦除,但java 畢竟是動態強類型語言,在實際使用過程當中,與老代碼結合的使用也會出現問題。

 

7 泛型的通配與上下界

泛型的通配表示的是爲知類型,經過? 表示

對一個泛型的通配有三種方式來使用它

類型 語法 Example
無界通配 ? List<?> l =new ArrayList<String>();

上界通配

? extends type List<? extends Exception> l
=new
ArrayList<RuntimeException>
();

下界通配

? super type List<? super Exception> l
=new
ArrayList<Object>();

 

7.1 無界通配

java 是強類型語言, 因此,對於

List<Object> keywords = new ArrayList<String>();

是不能經過編譯的, 若是使用了通配就可。

public static void printList(List<?> list) {
    for (Object x: list) System.out.println(x);
}
public static void main(String[] args) {
    List<String> keywords = new ArrayList<>();
    keywords.add("java");
    printList(keywords);
}

7.2 上界通配

假如 咱們要設定一個繼承關係的泛型

ArrayList<Number> list = new ArrayList<Integer>(); // DOES NOT COMPILE

 

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

上界通配表示 任何一個 Number的子類包括它本身均可以被匹配進來

public static long total(List<? extends Number> list) {
    long count = 0;
    for (Number number: list) count += number.longValue();
    return count;
}


// 有了上界的泛型,在基於泛型擦除的機制,會將Object 強轉成泛型上界


public static long total(List list) {
    long count = 0;
    for (Object obj: list) {
        Number number = (Number) obj;
        count += number.longValue();
    }
    return count;
}

須要注意的是,使用了上界通配的列表 是不能添加元素,從java的角度來看,編譯期並不知道

添加的元素的具體是哪個,由於任何extends type均可能。

static class Sparrow extends Bird { }
static class Bird { }
public static void main(String[] args) {
    List<? extends Bird> birds = new ArrayList<Bird>();
    birds.add(new Sparrow()); // DOES NOT COMPILE
    birds.add(new Bird()); // DOES NOT COMPILE
}

 

7.3 下界通配

與上界通配相似,表示 任何一個 超類包括它本身均可以被匹配進來

public static void addSound(List<? super String> list) { 
// lower bound
    list.add("quack");
}

8 總結

使用場景

在使用泛型的時候能夠遵循一些基本的原則,從而避免一些常見的問題。

  • 在代碼中避免泛型類和原始類型的混用。好比List 和List不該該共同使用。這樣會產生一些編譯器警告和潛在的運行時異常。當須要利用JDK 5以前開發的遺留代碼,而不得不這麼作時,也儘量的隔離相關的代碼。
  • 在使用帶通配符的泛型類的時候,須要明確通配符所表明的一組類型的概念。因爲具體的類型是未知的,不少操做是不容許的。
  • 泛型類最好不要同數組一塊使用。你只能建立new List<?>[10]這樣的數組,沒法建立new List[10]這樣的。這限制了數組的使用能力,並且會帶來不少費解的問題。所以,當須要相似數組的功能時候,使用集合類便可。
  • 不要忽視編譯器給出的警告信息。

PECS 原則

  • 若是要從集合中讀取類型T的數據, 而且不能寫入,可使用 上界通配符(<?extends>)—Producer Extends。
  • 若是要從集合中寫入類型T 的數據, 而且不須要讀取,可使用下界通配符(<? super>)—Consumer Super。
  • 若是既要存又要取, 那麼就要使用任何通配符。
相關文章
相關標籤/搜索