Java泛型全掌握

泛型是什么

在我們的實際工作中 泛型(Generics) 是無處不在的募书,我們也寫過不少莹捡,看到的更多扣甲,如,源碼涣脚、開源框架... 隨處可見寥茫,但是纱耻,我們真正理解泛型嗎?理解多少呢玖喘?例如:Box 累奈、Box<Object> 急但、Box<?> 羊始、Box<T> 突委、Box<? extends T> 、Box<? super T> 之間的區(qū)別是什么缘缚?

將類型由原來的具體的類型參數(shù)化桥滨,類似于方法中的變量參數(shù)弛车,此時類型也定義成參數(shù)形式(可以稱之為類型形 參)纷跛,然后在使用/調(diào)用時傳入具體的類型(類型實參)贫奠。
泛型的本質(zhì)是為了參數(shù)化類型(在不創(chuàng)建新的類型的情況下望蜡,通過泛型指定的不同類型來控制形參具體限制的類型)脖律。也就是說 在泛型使用過程中小泉,操作的數(shù)據(jù)類型被指定為一個參數(shù)兜挨,這種參數(shù)類型可以用在類拌汇、接口和方法中,分別被稱為泛型類魁淳、泛型接口界逛、 泛型方法纺座。

為什么我們需要泛型

示例一:如果我們需要一個功能净响,如果參數(shù)類型每改變一次馋贤,就要再重新寫一遍方法,那么代碼就會產(chǎn)生冗余仿滔。

public class NonGeneric {

    public int addInt(int a, int b) {
        return a + b;
    }

    public Float addFloat(float a, float b) {
        return a + b;
    }

    public static void main(String[] args) {
        NonGeneric nonGeneric = new NonGeneric();

        System.out.println(nonGeneric.addInt(1, 2));
        System.out.println(nonGeneric.addFloat(1f, 2f));
    }
}

示例二:

        ArrayList list = new ArrayList();
        list.add("cycy");
        list.add(1);

        for (int i=0;i<list.size();i++) {
            String name = list.get(i);
            System.out.println(name);
        }

這里程序會報錯:兼容的類型: Object無法轉(zhuǎn)換為String崎页。list默認(rèn)的類型會是Object類型实昨,如果不會String類型荒给,那么轉(zhuǎn)為String類型就會報錯刁卜。

那么泛型就能解決如上兩種情況的問題蛔趴,使用泛型可以使多種數(shù)據(jù)類型使用同一段代碼孝情,泛型能在編譯期校驗出不合法的類型。

為什么要使用泛型
  • 消除類型轉(zhuǎn)換
  • 在編譯時進(jìn)行更強(qiáng)的類型檢查
  • 增加代碼的復(fù)用性
泛型類魁亦,泛型接口洁奈,泛型方法的定義

定義泛型類利术,如T就能代表一個任意的類型印叁。

class NormalGeneric<T> {
    private T data;

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

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static void main(String[] args) {
        NormalGeneric normalGeneric = new NormalGeneric<String>("data");
        System.out.println(normalGeneric.getData());
    }
}

泛型類型個數(shù)可以不定:

/**
 * 多類型參數(shù)
 */
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<>("lili", 18);
        System.out.println("key:" + m1.getKey() + ", value:" + m1.getValue());

        MultipleGeneric<String, String> m2 = new ImplMultipleGeneric<>("name", "sign");
        System.out.println("key:" + m2.getKey() + ", value:" + m2.getValue());
    }
}
泛型類型的繼承規(guī)則

定義泛型接口轮蜕,這里再使用泛型類去實現(xiàn)肠虽,對于泛型類型的繼承玛追,可以繼續(xù)使用泛型類型痊剖,也可以在繼承中去指定具體類型陆馁。

interface Generitor<T> {
    T next();
}

public class Generitorimpl<T> implements Generitor<T> {
    @Override
    public T next() {
        return null;
    }
}

public class Generitorimpl<T> implements Generitor<String> {
    @Override
    public String next() {
        return null;
    }
}

定義泛型方法并使用:

class GenericMethod {
    public <T> T genericMethod(T ...a) {
        return a[a.length-1];
    }

    public static void main(String[] args) {
        //聲明調(diào)用genericMethod方法的泛型為String類型
        String name = new GenericMethod().<String>genericMethod("xiaoming", "xiaohong", "xiaowang");
        int num = new GenericMethod().genericMethod(1, 2, 3);
        System.out.println(name);
        System.out.println(num);
    }
}
泛型類里定義泛型方法
/**
 * 泛型類
 * @param <T>
 */
public class ShowClass <T> {

    /**
     * 定義普通方法
     */
    public void show1(T t) {
        System.out.println(t.toString());
    }

    /**
     * 定義泛型方法
     * @param e
     * @param <E>
     */
    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);
        showClass.show1(person); //編譯器會報錯击狮,因為 ShowClass<Fruit> 已經(jīng)限定類型

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

        showClass.show3(apple); //可以放入膘茎,泛型方法 <T> 和泛型類中的 <T> 不是同一條 T披坏,可以是任何非基本類型
        showClass.show3(person); //可以放入棒拂,泛型方法 <T> 和泛型類中的 <T> 不是同一條 T娘扩,可以是任何非基本類型

    }
}

在泛型類中定義泛型方法時琐旁,需要注意灰殴,泛型類里的泛型參數(shù) <T> 和泛型方法里的泛型參數(shù) <T> 不是同一個。

限定類型變量

在泛型中伟阔,我們要求泛型類型有一定的特征皱炉,我們就要對泛型類型做某種限定:

限定類型參數(shù)的泛型類:

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<>(); //只能傳入實現(xiàn)了 Comparable 接口的類型
        boundedClass.setT("IOS");
        System.out.println(boundedClass.min("Android"));
    }
}

限定類型參數(shù)的泛型方法:

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));
    }
}
多重限定

多個限定參數(shù),如果其中有類灾部,類必須放在第一個位置惯退,例如:

interface A {}

interface B {}

class C {}

public class MultipleBounds <T extends C & A & B>{

}
通配符類型

常用的通配符有常用的 T,E,K锁蠕,V夷野,?

T (type) 表示具體的一個java類型
K V (key value) 分別代表java鍵值中的Key Value
E (element) 代表Element
匿沛? 表示不確定的 java 類型

上界通配符 < ? extends E>

在類型參數(shù)中使用 extends 表示這個泛型中的參數(shù)必須是 E 或者 E 的子類扫责。

下界通配符 < ? super E>

在類型參數(shù)中使用 super 表示這個泛型中的參數(shù)必須是 E 或者 E 的父類。

通配符和子類型

例如Orange 是 Fruit 的子類逃呼,但是,F(xiàn)ruitPlateGen<Orange> 不是 FruitPlateGen<Fruit> 的子類者娱。
但是,你可以使用通配符在泛型類或接口之間創(chuàng)建關(guān)系黄鳍。

代碼如下:

        List<Apple> apples = new ArrayList<>();
        List<Fruit> fruits = apples;

編譯不過推姻,因為,List<Apple> 不是 List<Fruit> 的子類框沟,實際上這兩者無關(guān)藏古,那么,它們的關(guān)系是什么忍燥?
List<Apple> 和 List<Fruit> 的公共父級是 List<?>拧晕。

我們可以使用上下限通配符,在這些類之間創(chuàng)建關(guān)系梅垄,可以使承接通過厂捞,如下:

        List<Apple> apples = new ArrayList<>();
        List<? extends Fruit> fruits1 = apples; //第一種方式承接
        List<? super Apple> fruits2 = apples; //第二種方式承接
?和 T 的區(qū)別

队丝?和 T 都表示不確定的類型靡馁,區(qū)別在于我們可以對 T 進(jìn)行操作,但是對 机久?不行臭墨,比如如下這種 :

// 可以
T t = operate();

// 不可以
?car = operate();
泛型中的約束和局限性

如下3種使用泛型方式都是錯誤的:

class GenericLimit<T> {
    private T data;

    public GenericLimit() {
        data = new T();  //1.泛型類型不能直接創(chuàng)建對象
    }

    private static T instance;  //2.靜態(tài)變量不能用泛型類型聲明

    private void createInstance() {  
        GenericLimit<double> instance = new GenericLimit<double>();  //3.基本數(shù)據(jù)類型不能用作泛型變量
    }
}
  • 虛擬機(jī)是如何實現(xiàn)泛型的

泛型本質(zhì)是將數(shù)據(jù)類型參數(shù)化膘盖,它通過擦除的方式來實現(xiàn)胧弛。聲明了泛型的 .java 源代碼,在編譯生成 .class 文件之后衔憨,泛型相關(guān)的信息就消失了叶圃。可以認(rèn)為践图,源代碼中泛型相關(guān)的信息掺冠,就是提供給編譯器用的。泛型信息對 Java 編譯器可以見,對 Java 虛擬機(jī)不可見德崭。

Java 編譯器通過如下方式實現(xiàn)擦除:
用 Object 或者界定類型替代泛型斥黑,產(chǎn)生的字節(jié)碼中只包含了原始的類,接口和方法眉厨;
在恰當(dāng)?shù)奈恢貌迦霃?qiáng)制轉(zhuǎn)換代碼來確保類型安全锌奴;
在繼承了泛型類或接口的類中插入橋接方法來保留多態(tà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; }
  ...
}

由于類型參數(shù) 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; }
  ...
}
  • 泛型中類型擦除是什么

泛型信息只存在于代碼編譯階段,在進(jìn)入 JVM 之前服球,與泛型相關(guān)的信息會被擦除掉茴恰,專業(yè)術(shù)語叫做類型擦除。通俗地講斩熊,泛型類和普通類在 java 虛擬機(jī)內(nèi)是沒有什么特別的地方往枣。
舉例:

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

打印的結(jié)果為 true 是因為 List<String>和 List<Integer>在 jvm 中的 Class 都是 List.class。

協(xié)變

我們假設(shè)有三個類粉渠,動物分冈、貓、狗霸株。父類是動物Animal雕沉,有兩個子類貓Cat和狗Dog。

class Animal {}

class Dog extends Animal {}

class Cat extends Animal {}

那么由多態(tài)性可以有如下寫法:

Animal animalOne = new Cat();
Animal animalTwo = new Dog();

如果一只貓是一只動物淳衙,那一群貓是一群動物嗎蘑秽?一群狗是一群動物嗎?Java數(shù)組認(rèn)為是的箫攀。

//編譯通過
        Animal[] animals = new Cat[10];

我往這一群動物中添加一只貓肠牲、一只狗,它還是一群動物吧:

  Animal[] animals = new Cat[10];
        animals[0] = new Cat();
        // 下面這行代碼會拋運(yùn)行時異常
        animals[1] = new Dog();
        Animal animal = animals[0];

編譯沒有任何問題靴跛。但是一運(yùn)行缀雳,會拋出一個運(yùn)行時異常:ArrayStoreException。這個異常頭頂?shù)淖⑨屢呀?jīng)寫得很明顯了梢睛,如果你往數(shù)組中添加一個類型不對的對象肥印,就會拋這個異常。animals雖然門面上是一個Animal數(shù)組绝葡,但是它運(yùn)行時的本質(zhì)還是一個Cat數(shù)組啊深碱,一個Cat數(shù)組怎么能添加一個Dog呢?

如果Cat是Animal的子類型藏畅,那么Cat[]也是Animal[]的子類型敷硅,我們稱這種性質(zhì)為協(xié)變(covariance)。Java中,數(shù)組是協(xié)變的绞蹦。

  • 泛型的不變性
    而Java中的泛型是不變(invariance)的力奋,也就是說,List<Cat>并不是List<Animal>的子類型幽七。
        List<Cat> cats = new LinkedList<>();
        // 編譯器報錯
        List<Animal> animals = cats;
        //編譯通過
        List<? extends Animal> animals2 = cats;

雖然因為泛型的不變性景殷,List<Cat>并不是List<Animal>的子類型,但Java通過其它方式來支持了泛型的協(xié)變澡屡,List<Cat>是List<? extends Animal>的子類型猿挚。與此同時,Java在編譯器層面通過禁止寫入的方式挪蹭,保證了協(xié)變下的安全性亭饵。

協(xié)變不可添加元素:

List<? extends Animal> animals = new LinkedList<Cat>();
// 以下四行代碼都不能編譯通過
// animals.add(new Dog());
// animals.add(new Cat());
// animals.add(new Animal());
// animals.add(new Object());
// 可以添加null,但沒意義
animals.add(null);
// 可以安全地取出來
Animal animal = animals.get(0);
逆變
// 下面這行代碼編譯不通過
// List<? super Animal> animals = new LinkedList<Cat>();
// 下面都是OK的寫法
// List<? super Animal> animals = new LinkedList<Object>();
// List<? super Animal> animals = new LinkedList<Animal>();
// 等價于上面一行的寫法
List<? super Animal> animals = new LinkedList<>();
animals.add(new Cat());
animals.add(new Dog());
// 取出來一定是Object
Object object = animals.get(0);

// 這樣寫是OK的
List<? super Cat> cats = new LinkedList<Animal>();

逆變(contravariance)梁厉,也稱逆協(xié)變,從名字可以看出來踏兜,它與協(xié)變的性質(zhì)是相反的词顾。也就是說,List<Animal>是List<? super Cat>的子類型碱妆。

參考:
https://segmentfault.com/a/1190000023379970
https://juejin.cn/post/6911302681583681544

Github demo地址:

https://github.com/running-libo/GenericAnnotationReflect

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肉盹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子疹尾,更是在濱河造成了極大的恐慌上忍,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,294評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纳本,死亡現(xiàn)場離奇詭異窍蓝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)繁成,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,493評論 3 385
  • 文/潘曉璐 我一進(jìn)店門吓笙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人巾腕,你說我怎么就攤上這事面睛。” “怎么了尊搬?”我有些...
    開封第一講書人閱讀 157,790評論 0 348
  • 文/不壞的土叔 我叫張陵叁鉴,是天一觀的道長。 經(jīng)常有香客問我佛寿,道長幌墓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,595評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮克锣,結(jié)果婚禮上茵肃,老公的妹妹穿的比我還像新娘。我一直安慰自己袭祟,他們只是感情好验残,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,718評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著巾乳,像睡著了一般您没。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上胆绊,一...
    開封第一講書人閱讀 49,906評論 1 290
  • 那天氨鹏,我揣著相機(jī)與錄音,去河邊找鬼压状。 笑死仆抵,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的种冬。 我是一名探鬼主播镣丑,決...
    沈念sama閱讀 39,053評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼娱两!你這毒婦竟也來了莺匠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,797評論 0 268
  • 序言:老撾萬榮一對情侶失蹤十兢,失蹤者是張志新(化名)和其女友劉穎趣竣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旱物,經(jīng)...
    沈念sama閱讀 44,250評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡遥缕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,570評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了异袄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片通砍。...
    茶點(diǎn)故事閱讀 38,711評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖烤蜕,靈堂內(nèi)的尸體忽然破棺而出封孙,到底是詐尸還是另有隱情,我是刑警寧澤讽营,帶...
    沈念sama閱讀 34,388評論 4 332
  • 正文 年R本政府宣布虎忌,位于F島的核電站,受9級特大地震影響橱鹏,放射性物質(zhì)發(fā)生泄漏膜蠢。R本人自食惡果不足惜堪藐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,018評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挑围。 院中可真熱鬧礁竞,春花似錦、人聲如沸杉辙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,796評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜘矢。三九已至狂男,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間品腹,已是汗流浹背岖食。 一陣腳步聲響...
    開封第一講書人閱讀 32,023評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舞吭,地道東北人泡垃。 一個月前我還...
    沈念sama閱讀 46,461評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像羡鸥,于是被迫代替她去往敵國和親兔毙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,595評論 2 350

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