Android:寫了這麼多代碼,你真的理解泛型嗎

generics

在咱們的實際工做中 泛型(Generics) 是無處不在的,咱們也寫過很多,看到的更多,如,源碼、開源框架... 隨處可見,可是,咱們真正理解泛型嗎?理解多少呢?例如:BoxBox<Object>Box<?>Box<T>Box<? extends T>Box<? super T> 之間的區別是什麼?本篇文章將會對 泛型(Generics) 進行全面的解析,讓咱們對泛型有更深刻的理解。html

本篇文章的示例代碼放在 Github 上,全部知識點,如圖:java

Lucy 喜歡吃🍊(爲何要使用泛型)

首先,經過一個盤子裝水果小故事來打開咱們的泛型探索之旅(咱們爲何要使用泛型),故事場景以下:android

Lucy 到 James 家作客,James 須要招待客人,且知道 Lucy 喜歡吃橘子🍊,因而使用水果盤裝滿了🍊來招待客人

這個場景怎麼用代碼表現呢,咱們來新建幾個類,以下:git

Fruit:水果類github

package entity;

public class Fruit {

    @Override
    public String toString() {

        return "This is Fruit";
    }
}

Apple:蘋果類,繼承水果類數組

package entity;

public class Apple extends Fruit {

    @Override
    public String toString() {

        return " Apple 🍎";
    }
}

Orange:橘子類,繼承水果類安全

package entity;

public class Orange extends Fruit {

    @Override
    public String toString() {

        return " Orange 🍊";
    }
}

Plate:水果盤接口bash

package entity;

public interface Plate<T> {

    public void set(T t);

    public T get();

}

FruitPlate:水果盤類,實現水果盤接口app

package entity;

import java.util.ArrayList;
import java.util.List;

public class FruitPlate implements Plate {

    private List items = new ArrayList(6);

    @Override
    public void set(Object o) {
        items.add(o);
    }

    @Override
    public Fruit get() {
        int index = items.size() - 1;
        if(index >= 0) return (Fruit) items.get(index);
        return null;
    }

}

AiFruitPlate:智能水果盤,實現水果盤接口框架

package entity;

import java.util.ArrayList;
import java.util.List;
/**
 * 使用泛型類定義
 * @param <T>
 */
public class AiFruitPlate<T> implements Plate<T> {

    private List<T> fruits = new ArrayList<T>(6);
    @Override
    public void set(T t) {
        fruits.add(t);
    }

    @Override
    public T get() {
        int index = fruits.size() - 1;
        if(index >= 0) return fruits.get(index);
        return null;
    }
}

Person:人類

package entity;

public class Person {

}

Lucy:Lucy類,繼承 Person 類,她擁有吃橘子的能力 eat

import entity.Orange;
import entity.Person;

public class Lucy extends Person {

    public void eat(Orange orange) {

        System.out.println("Lucy like eat" + orange);

    }

}

James:James類,繼承 Person 類,他擁有獲取水果盤的能力 getAiFruitPlate

import entity.*;

public class James extends Person {

    public FruitPlate getPlate() {
        return new FruitPlate();
    }

    public AiFruitPlate getAiFruitPlate() {
        return new AiFruitPlate();
    }

    public void addFruit(FruitPlate fruitPlate, Fruit fruit) {
        fruitPlate.set(fruit);
    }

    public void add(AiFruitPlate<Orange> aiFruitPlate, Orange orange) {
        aiFruitPlate.set(orange);
    }

}

Scenario:測試類

import entity.*;

public class Scenario {

    public static void main(String[] args) {
        scenario1();
        scenario2();
    }
    //沒有使用泛型
    private static void scenario1() {
        James james = new James();
        Lucy lucy = new Lucy();
        FruitPlate fruitPlate = james.getPlate(); // James 拿出水果盤
        james.addFruit(fruitPlate,new Orange()); // James 往水果盤裏裝橘子
        lucy.eat((Orange) fruitPlate.get()); // 須要轉型爲 Orange
    }
    //使用了泛型
    private static void scenario2() {
        James james = new James();
        Lucy lucy = new Lucy();
        AiFruitPlate<Orange> aiFruitPlate = james.getAiFruitPlate(); // James 拿出智能水果盤(知道你須要裝橘子)
        james.add(aiFruitPlate, new Orange()); // James 往水果盤裏裝橘子(若是,裝的不是橘子會提醒)
        lucy.eat(aiFruitPlate.get()); // 不須要轉型
    }

}

運行結果,以下:

Lucy like eat  Orange 🍊
Lucy like eat  Orange 🍊

Process finished with exit code 0

咱們能夠很明顯的看出,使用了泛型以後,不須要類型轉換,若是,咱們把 scenario1() 方法,稍微改下,以下:

private static void scenario1() {
        James james = new James();
        Lucy lucy = new Lucy();
        FruitPlate fruitPlate = james.getPlate();
        james.addFruit(fruitPlate,new Apple()); //new Orange() 改爲 new Apple()
        lucy.eat((Orange) fruitPlate.get());
    }

編譯器不會提示有問題,可是運行以後報錯,以下:

Exception in thread "main" java.lang.ClassCastException: entity.Apple cannot be cast to entity.Orange
    at Scenario.scenario1(Scenario.java:21)
    at Scenario.main(Scenario.java:7)

Process finished with exit code 1

而,咱們把 scenario2() (使用了泛型)作出一樣的修改,以下:

private static void scenario2() {
        James james = new James();
        Lucy lucy = new Lucy();
        AiFruitPlate<Orange> aiFruitPlate = james.getAiFruitPlate();
        james.add(aiFruitPlate, new Apple());
        lucy.eat(aiFruitPlate.get());
    }

編譯器,會提示咱們有錯誤,如圖:

error

經過以上案例,很清晰的知道咱們爲何要使用泛型,以下:

  • 消除類型轉換
  • 在編譯時進行更強的類型檢查
  • 增長代碼的複用性

泛型類(Generic Class)

泛型類是經過類型進行參數化的類,這樣說可能不是很好理解,以後咱們用代碼演示。

普通類(A Simple Class)

首先,咱們來定義一個普通的類,以下:

package definegeneric;

public class SimpleClass {

    private Object object;

    public Object getObject() {
        return object;
    }

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

它的 getset 方法接受和返回一個 Object,因此,咱們能夠隨意的傳遞任何類型。在編譯時沒法檢查類型的使用,咱們能夠傳入 Integer 且取出 Integer,也能夠傳入 String ,從而容易致使運行時錯誤。

泛型類(A Generic Class)

泛型類的定義格式以下:

class name<T1,T2,...,Tn>{
  ...
}

在類名以後的 <> 尖括號,稱之爲類型參數(類型變量),定義一個泛型類就是使用 <> 給它定義類型參數:T一、T2 ... Tn。

而後,咱們把 SimpleClass 改爲泛型類,以下:

package definegeneric;

public class GenericClass<T> {

    private T t;

    public T getT() {
        return t;
    }

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

因此的 object 都替換成爲 T,類型參數能夠定義爲任何的非基本類型,如:class類型、interface類型、數組類型、甚至是另外一個類型參數。

調用和實例化泛型類型(nvoking and Instantiating a Generic Type)

要想使用泛型類,必須執行泛型類調用,如:

GenericClass<String> genericClass;

泛型類的調用相似於方法的調用(傳遞了一個參數),可是,咱們沒有將參數傳遞給方法,而是,將類型參數(String)傳遞給了 GenericClass 類自己。

此代碼不會建立新的 GenericClass 對象,它只是聲明瞭 genericClass 將保存對 String 的引用

要實例化此類,要使用 new 關鍵字,如:

GenericClass<String> genericClass = new GenericClass<String>();

或者

GenericClass<String> genericClass = new GenericClass<>();

在 Java SE 7 或更高的版本中,編譯器能夠從上下文推斷出類型參數,所以,可使用 <> 替換泛型類的構造函數所需的類型參數

類型參數命名規範(Type Parameter Naming Conventions)

咱們的類型參數是否必定要寫成 T 呢,按照規範,類型參數名稱是單個大寫字母。

經常使用的類型參數名稱有,如:

類型參數 含義
E Element
K Key
N Number
V Value
S,U,V... 2nd, 3rd, 4th type

多類型參數(Multiple Type Parameters)

泛型類能夠有多個類型參數,如:

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

public class ImplMultipleGeneric<K, V> implements MultipleGeneric<K, V> {

    private K key;
    private V value;

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

    @Override
    public K getKey() {
        return key;
    }

    @Override
    public V getValue() {
        return value;
    }

    public static void main(String[] args) {
        MultipleGeneric<String, Integer> m1 = new ImplMultipleGeneric<String, Integer>("per",6);
        System.out.println("key:" + m1.getKey() + ", value:" + m1.getValue());

        MultipleGeneric<String,String> m2 = new ImplMultipleGeneric<String, String>("per","lsy");
        System.out.println("key:" + m2.getKey() + ", value:" + m2.getValue());
    }
}

輸出結果:

key:per, value:6
key:per, value:lsy

Process finished with exit code 0

如上代碼,new ImplMultipleGenericK 實例化爲 String,將 V 實例化爲 Integer ,所以, ImplMultipleGeneric 構造函數參數類型分別爲 StringInteger,在編寫 new ImplMultipleGeneric 代碼時,編輯器會自動填寫 <> 的值

因爲,Java 編譯器會從聲明 ImplMultipleGeneric 推斷出 KV 的類型,所以咱們能夠簡寫爲,以下:

MultipleGeneric<String, Integer> m1 = new ImplMultipleGeneric<>("per",6);
System.out.println("key:" + m1.getKey() + ", value:" + m1.getValue());

MultipleGeneric<String,String> m2 = new ImplMultipleGeneric<>("per","lsy");
System.out.println("key:" + m2.getKey() + ", value:" + m2.getValue());

泛型接口(Generic Interface)

定義泛型接口和定義泛型類類似(泛型類的技術可同用於泛型接口),以下:

interface name<T1,T2,...,Tn>{
  ...
}

咱們來定義一個泛型接口,以下:

package definegeneric;

public interface Genertor<T> {
    public T next();
}

那麼,如何實現一個泛型接口呢,咱們使用兩種方式來實現泛型接口,以下:

使用泛型類,實現泛型接口,且不指定確切的類型參數,因此,實現的 next() 返回值自動變成 T

package definegeneric.impl;

import definegeneric.Genertor;

public class ImplGenertor<T> implements Genertor<T> {

    @Override
    public T next() {
        return null;
    }
}

使用普通類,實現泛型接口,且指定確切的類型參數爲 String,因此,實現的 next() 返回值自動變成 String

package definegeneric.impl;

import definegeneric.Genertor;

public class ImplGenertor2 implements Genertor<String> {

    @Override
    public String next() {
        return null;
    }
}

泛型方法(Generic Methods)

泛型方法使用了類型參數的方法,泛型方法比較獨立,能夠聲明在 普通類、泛型類、普通接口、泛型接口中。

泛型方法定義格式,以下:

public <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2)

泛型方法的類型參數列表,在 <> 內,該列表必須在方法返回類型以前;對於靜態的泛型方法,類型參數必須在 static 以後,方法返回類型以前。

普通類裏定義泛型方法(Generic methods in a Simple Class)

咱們在普通類中定義泛型方法,以下:

package methodgeneric;

public class MethodGeneric {

    //定義一個泛型方法
    public <T> T genericMethod(T...t) {
        return t[t.length/2];
    }

    public static void main(String[] args) {
        MethodGeneric methodGeneric = new MethodGeneric();
        System.out.println(methodGeneric.<String>genericMethod("java","dart","kotlin"));
    }
}

methodGeneric.<String>genericMethod("java","dart","kotlin") 一般能夠省略掉 <> 的內容,編譯器將推斷出所需的類型,和調用普通方法同樣,如:

methodGeneric.genericMethod("java","dart","kotlin")

泛型類裏定義泛型方法(Generic methods in a Generic Class)

咱們在泛型類中定義泛型方法,以下:

package methodgeneric;

public class MethodGeneric2 {

    static class Fruit{

        @Override
        public String toString() {
            return "fruit";
        }
    }

    static class Apple extends Fruit {

        @Override
        public String toString() {
            return "Apple";
        }
    }

    static class Person{

        @Override
        public String toString() {
            return "person";
        }
    }
    //定義了泛型類
    static class ShowClass<T> {
        //定義了普通方法
        public void show1(T t){
            System.out.println(t.toString());
        }
        //定義了泛型方法
        public <E> void show2(E e) {
            System.out.println(e.toString());
        }
        //定義了泛型方法
        public <T> void show3(T t) {
            System.out.println(t.toString());
        }
    }

    public static void main(String[] args) {

        Apple apple = new Apple();
        Person person = new Person();

        ShowClass<Fruit> showClass = new ShowClass<>();
        showClass.show1(apple);   //能夠放入 apple,由於 apple 是 fruit 的子類
        showClass.show1(person); //此時,編譯器會報錯,由於 ShowClass<Fruit> 已經限定類型

        showClass.show2(apple); //能夠放入,泛型方法 <E> 能夠是任何非基本類型
        showClass.show2(person);//能夠放入,泛型方法 <E> 能夠是任何非基本類型

        showClass.show3(apple); //能夠放入,泛型方法 <T> 和泛型類中的 <T> 不是同一條 T,能夠是任何非基本類型
        showClass.show3(person); //能夠放入,泛型方法 <T> 和泛型類中的 <T> 不是同一條 T,能夠是任何非基本類型
    }
}

在泛型類中定義泛型方法時,須要注意,泛型類裏的泛型參數 <T> 和泛型方法裏的泛型參數 <T> 不是同一個。

限定類型參數(Bounded Type Parameters)

咱們常常看到相似 public <U extends Number> void inspect(U u) 的代碼,<U extends Number> 就是限制類型參數,只對數字進行操做且只接受 Number 或其子類。

要聲明一個限定的類型參數,須要在參數類型後加上 extends 關鍵字,而後是其上限類型(類或接口)。

限定類型參數的泛型類(Generic Class of Bounded Type Parameters)

泛型類也可使用限定類型參數,以下:

package boundedgeneric;

public class BoundedClass<T extends Comparable> {

    private T t;

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

    public T min(T outter){
        if(this.t.compareTo(outter) > 0)
            return outter;
        else
            return this.t;
    }

    public static void main(String[] args) {
        BoundedClass<String> boundedClass = new BoundedClass<>(); //只能傳入實現了 Comparable 接口的類型
        boundedClass.setT("iOS");
        System.out.println(boundedClass.min("android"));
    }
}

限定類型參數的泛型方法(Generic methods of Bounded Type Parameters)

泛型方法也可使用限定類型參數,以下:

package boundedgeneric;

public class BoundedGeneric {

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

    public static void main(String[] args) {
        System.out.println(BoundedGeneric.min(66,666));
    }
}

多重限定(Multiple Bounds)

限定類型參數,也能夠爲多個限定,如:

<T extends B1 & B2 & B3>

多個限定參數,若是其中有類,類必須放在第一個位置,例如:

interface A { ... }
interface B { ... }
class C { ... }

class D <T extends C & A & B>

泛型,繼承和子類型(Generics, Inheritance, and Subtypes)

在前面的盤子裝水果小故事裏咱們已經建立好了一些水果類,以下:

public class Fruit {
    @Override
    public String toString() {
        return "This is Fruit";
    }
}

public class Apple extends Fruit {
    @Override
    public String toString() {
        return " Apple 🍎";
    }
}

public class Orange extends Fruit {
    @Override
    public String toString() {
        return " Orange 🍊";
    }
}

public class QIOrange extends Orange {
    @Override
    public String toString() {
        return "qi Orange 🍊";
    }
}

他們的繼承關係,如圖:

no-shadow

衆所周知,咱們能夠把子類賦值給父類,例如:

Apple apple = new Apple();
Fruit fruit = new Fruit();
fruit = apple;

泛型也是如此,咱們定義一個水果盤子的泛型類,以下:

public class FruitPlateGen<Fruit> implements Plate<Fruit> {

    private List<Fruit> fruits = new ArrayList<>(6);

    @Override
    public void set(Fruit fruit) {
        fruits.add(fruit);
    }

    @Override
    public Fruit get() {
        int index = fruits.size() - 1;
        if(index >= 0) return fruits.get(index);
        return null;
    }
}

因此,是 Fruit 的子類均可以放入水果盤裏,以下:

FruitPlateGen<Fruit> fruitPlate = new FruitPlateGen<Fruit>();
fruitPlate.set(new Apple());
fruitPlate.set(new Orange());

如今,James 能夠獲取盤子,以下:

public class James extends Person {
    public FruitPlateGen getAiFruitPlateGen(FruitPlateGen<Fruit> plate) {
        return new FruitPlateGen();
    }
}

如是,James 想獲取放橘子的盤子,以下:

James james = new James();
james.getAiFruitPlateGen(new FruitPlateGen<Fruit>()); //獲取成功
james.getAiFruitPlateGen(new FruitPlateGen<Orange>()); //編譯器報錯

雖然,OrangeFruit 的子類,可是,FruitPlateGen<Orange> 不是 FruitPlateGen<Fruit> 的子類,因此,不能傳遞產生繼承關係。

泛型類和子類型(Generic Classes and Subtyping)

咱們能夠經過繼承(extends)或實現(implements)泛型類或接口,例如:

private static class ExtendFruitPlate<Orange> extends FruitPlateGen<Fruit> {

}

此時,ExtendFruitPlate<Orange> 就是 FruitPlateGen<Fruit> 的子類,James 再去拿盤子,就不會有錯誤提示:

james.getAiFruitPlateGen(new ExtendFruitPlate<Orange>());

通配符(Wildcards)

咱們常常看到相似 List<? extends Number> 的代碼,? 就是通配符,表示未知類型。

上限通配符(Upper Bounded Wildcards)

咱們可使用上限通配符來放寬對變量的限制,例如,上文提到的 FruitPlateGen<Fruit>FruitPlateGen<Orange>() 就可使用上限通配符。

咱們來改寫一下 getAiFruitPlateGen 方法,以下:

public FruitPlateGen getAiFruitPlateGen2(FruitPlateGen<? extends Fruit> plate) {
    return new FruitPlateGen();
}

這時候,James 想獲取放橘子的盤子,以下:

James james = new James();
james.getAiFruitPlateGen2(new FruitPlateGen<Fruit>()); //獲取成功
james.getAiFruitPlateGen2(new FruitPlateGen<Orange>()); //獲取成功

上限通配符 FruitPlateGen<? extends Fruit> 匹配 FruitFruit 的任何子類型,因此,咱們能夠傳入 AppleOrange 都沒有問題。

下限通配符(Lower Bounded Wildcards)

上限通配符將未知類型限定爲該類型或其子類型,使用 extends 關鍵字,而下限通配符將未知類型限定爲該類型或其父類型,使用 super 關鍵字。

咱們再來寬展一下 getAiFruitPlateGen 方法,以下:

public FruitPlateGen getAiFruitPlateGen3(FruitPlateGen<? super Apple> plate) {
    return new FruitPlateGen();
}

這時候,James 只能獲取 FruitPlateGen<Fruit>FruitPlateGen<Apple> 的盤子,以下:

James james = new James();
james.getAiFruitPlateGen3(new FruitPlateGen<Apple>());
james.getAiFruitPlateGen3(new FruitPlateGen<Fruit>());

下限通配符 FruitPlateGen<? super Apple> 匹配 AppleApple 的任何父類型,因此,咱們能夠傳入 AppleFruit

通配符和子類型(Wildcards and Subtyping)

泛型,繼承和子類型 章節有講到,雖然,OrangeFruit 的子類,可是,FruitPlateGen<Orange> 不是 FruitPlateGen<Fruit> 的子類。可是,你可使用通配符在泛型類或接口之間建立關係。

咱們再來回顧下 Fruit 的繼承關係,如圖:

代碼,以下:

Apple apple = new Apple();
Fruit fruit = apple;

這個代碼是沒有問題的,FruitApple 的父類,因此,能夠把子類賦值給父類。

代碼以下:

List<Apple> apples = new ArrayList<>();
List<Fruit> fruits = apples; // 編輯器報錯

由於,List<Apple> 不是 List<Fruit> 的子類,實際上這二者無關,那麼,它們的關係是什麼?如圖:

List<Apple>List<Fruit> 的公共父級是 List<?>

咱們可使用上下限通配符,在這些類之間建立關係,以下:

List<Apple> apples = new ArrayList<>();
List<? extends Fruit> fruits1 = apples; // OK
List<? super Apple> fruits2 = apples; // OK

下圖展現了上下限通配符聲明的幾個類的關係,如圖:

PECS原則(Producer extends Consumer super)

在上文中有 FruitPlateGen 水果盤子的類,咱們嘗試使用上下限通配符來實例化水果盤,代碼以下:

Apple apple = new Apple();
Orange orange = new Orange();
Fruit fruit = new Fruit();

FruitPlateGen<? extends Fruit> fruitPlateGen = new FruitPlateGen<>();
fruitPlateGen.set(apple); // error
fruitPlateGen.set(orange); // error
fruitPlateGen.set(fruit); // error
Fruit fruit1 = fruitPlateGen.get(); // OK
Orange orange1 = fruitPlateGen.get(); // error
Apple apple1 = fruitPlateGen.get(); // error

上限通配符沒法 set 數據,可是,能夠 get 數據且只能 get 到其上限 Fruit,因此,上限通配符能夠安全的訪問數據。

在來看一下代碼,以下:

FruitPlateGen<? super Apple> fruitPlateGen1 = new FruitPlateGen<>();
fruitPlateGen1.set(apple); // OK
fruitPlateGen1.set(orange); // error
fruitPlateGen1.set(fruit); // error
Object object = fruitPlateGen1.get(); // OK
Fruit fruit2 = fruitPlateGen1.get(); // error
Apple apple2 = fruitPlateGen1.get(); // error
Orange orange2 = fruitPlateGen1.get(); // error

下限通配符能夠且只能 set 其下限 Apple,也能夠 get 數據,但只能用 Object 接收(由於Object是全部類型的父類,這是一個特例),因此,下限通配符能夠安全的寫入數據。

因此,在使用上下限通配符時,能夠遵循如下準則:

  • 若是你只須要從集合中得到類型T , 使用<? extends T>通配符
  • 若是你只須要將類型T放到集合中, 使用<? super T>通配符
  • 若是你既要獲取又要放置元素,則不使用任何通配符

類型擦除(Type Erasure)

Java 語言使用類型擦除機制實現了泛型,類型擦除機制,以下:

  • 編譯器會把全部的類型參數替換爲其邊界(上下限)或 Object,所以,編譯出的字節碼中只包含普通類、接口和方法。
  • 在必要時插入類型轉換,已保持類型安全
  • 生成橋接方法以在擴展泛型類時保持多態性

泛型類型的擦除(Erasure of Generic Types)

Java 編譯器在擦除過程當中,會擦除全部類型參數,若是類型參數是有界的,則替換爲第一個邊界,若是是無界的,則替換爲 Object。

咱們定義了一個泛型類,代碼以下:

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

因爲類型參數 T 是無界的,所以,Java 編譯器將其替換爲 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; }
  ...
}

咱們再來定義一個有界的泛型類,代碼以下:

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

Java 編譯器其替換爲第一個邊界 Comparable,以下:

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

泛型方法的擦除(Erasure of Generic Methods)

Java 編譯器一樣會擦除泛型方法中的類型參數,例如:

public static <T> int count(T[] anArray, T elem) {
  int cnt = 0;
  for (T e : anArray)
}

因爲 T 是無界的,所以,Java 編譯器將其替換爲 Object,以下:

public static int count(Object[] anArray, Object elem) {
  int cnt = 0;
  for (Object e : anArray) if (e.equals(elem))
}

以下代碼:

class Shape {  ...  }
class Circle extends Shape {  ...  } 
class Rectangle extends Shape {  ...  }

有一個泛型方法,以下:

public static<T extends Shape> void draw(T shape){
  ...
}

Java 編譯器將用第一個邊界 Shape 替換 T,以下:

public static void draw(Shape shape){
  ...
}

橋接方法(Bridge Methods)

有時類型擦除會致使沒法預料的狀況,以下:

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);
  } 
}

類型擦除後,代碼以下:

public class Node {
  public Object data;
  public Node(Object data) { this.data = data; }
  public void setData(Object data) { 
    System.out.println("Node.setData"); 
    this.data = data;
  } 
}
public class MyNode extends Node {
  public MyNode(Integer data) { super(data); }
  public void setData(Integer data) { 
    System.out.println("MyNode.setData");
    super.setData(data);
  } 
}

此時,Node 的方法變爲 setData(Object data) 和 MyNode 的 setData(Integer data) 不會覆蓋。

爲了解決此問題並保留泛型類型的多態性,Java 編譯器會生成一個橋接方法,以下:

class MyNode extends Node {
  // 生成的橋接方法
  public void setData(Object data) {
      setData((Integer) data);
  }
  public void setData(Integer data) { 
    System.out.println("MyNode.setData"); 
    super.setData(data);
  }
  ...
}

這樣 Node 的方法 setData(Object data) 和 MyNode 生成的橋接方法 setData(Object data) 能夠完成方法的覆蓋。

泛型的限制(Restrictions on Generics)

爲了有效的使用泛型,須要考慮如下限制:

  • 沒法實例化具備基本類型的泛型類型
  • 沒法建立類型參數的實例
  • 沒法聲明類型爲類型參數的靜態字段
  • 沒法將Casts或instanceof與參數化類型一塊兒使用
  • 沒法建立參數化類型的數組
  • 沒法建立,捕獲或拋出參數化類型的對象
  • 沒法重載每一個重載的形式參數類型都擦除爲相同原始類型的方法

沒法實例化具備基本類型的泛型類型

代碼以下:

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'); // error

沒法建立類型參數的實例

代碼以下:

public static <E> void append(List<E> list) {
   E elem = new E(); // error 
   list.add(elem);
}

沒法聲明類型爲類型參數的靜態字段

代碼以下:

public class MobileDevice<T> {
  private static T os; // error
  ...
}

類的靜態字段是全部非靜態對象共享的變量,所以,不容許使用類型參數的靜態字段。

沒法將Casts或instanceof與參數化類型一塊兒使用

代碼以下:

public static <E> void rtti(List<E> list) {
  if (list instanceof ArrayList<Integer>) { // error
    ...
  } 
}

Java 編譯器會擦除全部類型參數,全部,沒法驗證在運行時使用的參數化類型。

沒法建立參數化類型的數組

代碼以下:

List<Integer>[] arrayOfLists = new List<Integer>[2]; // error

沒法建立,捕獲或拋出參數化類型的對象

代碼以下:

class MathException<T> extends Exception {  ...  } // error
class QueueFullException<T> extends Throwable{ ... } // error

沒法重載每一個重載的形式參數類型都 擦除爲相同原始類型的方法

代碼以下:

public class Example {
  public void print(Set<String> strSet) { }
  public void print(Set<Integer> intSet) { }
}

print(Set<String> strSet)print(Set<Integer> intSet) 在類型擦除後是徹底相同的類型,因此,沒法重載。

最後,附上本身的博客和GitHub地址:以下

博客地址:https://h.lishaoy.net
GitHub地址:https://github.com/persilee

相關文章
相關標籤/搜索