《重學Java系列》之 泛型(上)

不詩意的女程序媛不是好廚師~
【轉載請註明出處,From李詩雨—https://blog.csdn.net/cjm2484836553/article/details/103278750

[篇外話]
最近給自己安排了不少的學習計劃,每天都有很多的書要看,課要聽,字要寫,感覺很充實~

越來越覺得,大腦是很容易欺騙我們的。很多東西瞟一眼,聽一句,就感覺我會我懂,但是要讓我說,讓我寫,就GG了。

在輸入知識之後,要及時的輸出,只有經過一進一出的反饋之後,纔會真正的學到點東西。

今天,我把自己拋空,重新學習。❤

關於泛型,我要說的有以下8點:
在這裏插入圖片描述


泛型的東西細細品起來還是不少的,所以我會分爲上下兩篇,
今天我們先來看前5點~

廢話也說的差不多了,下面就讓我們開始吧~

1.什麼是泛型?

泛型,即“參數化類型”。
你可能又會問,參數化類型又是什麼東東啊?

這個我們可以通過類比、擴展的方式來理解。
大家應該都知道參數吧,定義方法的時候我們有形參,當要調用該方法時,我們要傳進去實參。
其實,參數化類型我們可以這樣理解,就是類型由原來的“ 具體的類型參數化。我們定義的時候用 參數形式(可以類比爲形參),使用的時候再傳入具體的類型(可類比於形參)。

如果這樣說你還不太懂,沒關係的!

最近,詩雨正好看到了一個大佬的文章,用了一個更加生動形象的比喻來解釋泛型。我真的是佩服佩服,下面就請允許我來和大家分享吧:

一隻玻璃杯我們可以用來幹什麼呢?
我可以用它來盛一杯可樂,你可以用它來盛一杯江小白,然後我們還可以乾一杯。
泛型的概念就在於此,燒製完成這隻杯子的時候沒有必要在說明書上定義死,指明它只能盛可樂,卻不能盛江小白。也沒有必要在說明書上指明它用來盛液體,或許一個熊孩子會用它來裝彩虹糖呢。
這麼一說,你有沒有覺得形象很多?
泛型其實就是在定義類、接口、方法的時候不侷限地指定某一種特定類型,而是讓類、接口、方法的調用者自己來決定具體使用哪一種類型的參數。

現在有一隻玻璃杯,你可以讓它盛一杯可樂、雪碧,也可以盛一杯江小白——泛型的概念就在於此,製造這隻杯子的時候沒必要在說明書上定義死,指明它只能盛碳酸飲料而不能盛白酒!

就好比,玻璃杯的燒製者說,我不知道使用者用這隻玻璃杯來幹嘛,所以我只負責造這麼一隻杯子;玻璃杯的使用者說,這就對了,我來決定這隻玻璃杯是盛可樂還是白酒,或者彩虹糖。

真的是有才,這個比喻太形象了,我現已經可以泛型的意思了,你呢?

如果你也理解了,那就讓我們繼續往下看吧~

2.爲什麼要有泛型?

這個,我們可以從反面來論證。
如果沒有泛型,我們會遇到哪些問題呢?
我們先來看兩段小代碼:
(1). 我們在實際開發中經常遇到不同數據類型的求和問題:
如int類型的求和,float類型的求和,double類型的求和,如果我們通過重載的方式來實現,就會出現大量的重複代碼,如下所示:

    //int型求和
    public int add(int a, int b) {
        return a + b;
    }

    //float型求和
    public float add(float a, float b) {
        return a + b;
    }

    //double型求和
    public double add(double a, double b) {
        return a + b;
    }

那我們就想,此處要是有泛型的話,代碼的冗餘問題就會在一定程度上得到解決呢。

(2). 我們不使用泛型很可能會出現下面這種情況:
在這裏插入圖片描述
在編譯階段沒有問題,但是一旦運行起來就會報ClassCastException的錯:
在這裏插入圖片描述
你說,我本來是想取個帥哥出來,結果一不小心差點把他轉爲了數字,這當然是不行的。

如果我們使用泛型來做約束,那上面的這種情況就不會出現了:
在這裏插入圖片描述
一旦我們規定了加入List中的數據類型必須是String,那麼只要你添加的不符合,它在編譯期便會及時報錯,而不是要等到運行期間才遲遲告訴你錯了。而且取出的時候你也不用考慮類型轉換問題了,真是一舉兩得。

通過以上的實例我們不難看出 使用泛型的兩個優點

  • 適用於多種數據類型執行相同的代碼
  • 泛型中的類型在使用時指定,不需要強制類型轉換,而且可以儘早的避免錯誤。

好了,現在我們已經知道了什麼是泛型,爲什麼要有泛型,下面就讓我們來具體看看泛型在 類、接口 和 方法 中的使用吧~

3.泛型類、泛型接口、泛型方法

通過上面的學習,我們知道 泛型的本質就是爲了參數化類型,即在不創建新的類型的情況下,通過泛型指定的不同類型來控制形參具體限制的類型。也就是說在泛型使用過程中,操作的數據類型被指定爲一個參數,這種參數類型可以用在類、接口和方法中,分別被稱爲泛型類、泛型接口、泛型方法。

我們先來看看泛型類。
(1). 泛型類

  • 引入一個類型變量T,並且用<>括起來,並放在類名的後面。這樣就是一個泛型類了。
    在這裏插入圖片描述
    如下列代碼:
public class MyGeneric<T> {
    private T data;

    public MyGeneric() {
    }

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

    public T getData() {
        return data;
    }

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

  • 泛型類是允許有多個類型變量的。
    如下列代碼:
public class MyGeneric2<K,V> {
    private K key;
    private V value;

    public MyGeneric2() {
    }

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

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public void setValue(V value) {
        this.value = value;
    }
}

(PS:除了T,其他大寫字母都可以哦,不過常用的就是T,E,K,V等)

(2). 泛型接口
泛型接口與泛型類的定義基本相同。

public interface Generic<T> {
    public T doSth();
}

需要注意的是 實現泛型接口的類,有兩種實現方法:

①. 實現泛型接口的類,未傳入泛型實參

public class ImlGeneric<T> implements Generic<T> {
    private T data;

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

在new出類的實例時,需要指定具體類型:
在這裏插入圖片描述

②. 實現泛型接口的類,傳入泛型實參

public class ImpGeneric2 implements Generic<String> {
    @Override
    public String doSth() {
        return "突然好想你";
    }
}

在new出類的實例時,和普通的類沒區別。

(3). 泛型方法
泛型方法,是在調用方法的時候指明泛型的具體類型 ,泛型方法可以在任何地方和任何場景中使用,包括普通類和泛型類。

有沒有什麼技巧來辨認一個泛型方法呢?
答案是有的:在 方法的 修飾符 和 返回值 之間有一個 < T> 的 纔是泛型方法歐~
在這裏插入圖片描述
來一個泛型方法給大家看看:
在這裏插入圖片描述
再次牢記一下祕方 : 方法的 修飾符 < T> 返回值 三者只有這樣的排列纔是泛型方法偶~

好的,下面我們就趕快來測試一下,加深記憶:
測試1:下面這個可不是泛型方法哈!
在這裏插入圖片描述
測試2:下面這個纔是正真的泛型方法:
在這裏插入圖片描述
好的,現在我們泛型類、泛型接口、泛型方法也基本掌握了,又進步了一點點,是不是有點開心呢?下面讓我們繼續前行~

4.給類型變量增加約束

有時候,我們需要對類型變量加以約束。

比如計算兩個變量的最大值。
要找出最大,就免不了要進行比較。那麼問題來了,不是所有的類型都能進行比較啊?在這裏插入圖片描述
所以我們要求傳入的類型要能夠進行比較,最好有比較方法。是的,或許我們想到一塊去了,我也想到了要讓其實現Comparable接口。
在這裏插入圖片描述

T extends Comparable
T表示應該綁定類型的子類型,Comparable表示綁定類型,子類型和綁定類型可以是類也可以是接口。
如果這個時候,我們試圖傳入一個沒有實現接口Comparable的類的實例,將會發生編譯錯誤。
在這裏插入圖片描述
注意:

  • extends左右都允許有多個
    如 T,V extends Serializable & Comparable
  • 注意限定類型中,只允許有一個類,因爲類是單繼承的。
    如果限定類型中既有類又有接口,那這個類必須要放在第一個位置上,接口接着往後放就可以了。
  • 這種類的限定既可以用在泛型方法上也可以用在泛型類、泛型接口上。

另外需要注意理解區分一下:
因爲在Java中,<T, U>表示指定了2個泛型,T、U。
所以,在對泛型做限制限制時,

  • <T, U extends Comparable >
    表示泛型U限定了必須爲 Comparable 的子類,而T沒有。

  • <T extends Comparable, U extends Comparable >
    表示泛型T和U都限定爲 Comparable 的子類
    由於 Comparable 是個接口,因此“ Comparable 的子類 ” 正確的描述應該爲實現了 Comparable 接口的類

  • <T, U extends Comparable & Serializable>表示:
    U 有限制,爲現實了Comparable 和 Serializable 接口的類,
    T 爲任意類型,沒有限制

5. 泛型類型的繼承規則

泛型是怎樣的繼承規則呢?
讓我們來自己探討一下吧~
現在有兩個類,他們是“父與子的關係”:
在這裏插入圖片描述在這裏插入圖片描述
還有一個泛型類:
在這裏插入圖片描述
那麼問題來了:Generic< Pet> 和 Generic< Dog> 是什麼關係呢?
在這裏插入圖片描述
是的,結果如圖所示,Generic< Pet> 和 Generic< Dog>竟然沒有任何關係!
這關係可不是鬧着玩的,Generic< Pet>不信,非得要做個DNA去驗證,我們也理解他的心情就帶着他去做了:
在這裏插入圖片描述
現實總是骨幹,Pet 和 Dog 有 父與子 的關係,但是 Generic< Pet>和Generic< Dog>真的沒有任何關係!

好吧,我們就讓 Generic< Pet> 在廁所裏哭一會吧。

但是,注意啦,泛型類是可以繼承或者擴展其他泛型類,比如List和ArrayList。又比如下面的代碼:
public class PetGeneric<T> { }
public class DogPeneric<T> extends PetGeneric<T> { }
用的時候" PetGeneric dog=new DogPeneric<>();"這樣是沒有問題的。

好的,今天我們就先到這。
剩下的3點內容,我們下篇見吧~

積累點滴,做好自己~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章