Java泛型知識點

一、什麼是泛型

泛型就是參數化類型,即我們在定義的時候,將具體的類型進行參數化,在調用或者使用的時候,再傳入具體的參數類型,我們可以將泛型用在類、接口和方法中,分別被稱爲泛型類、泛型接口、泛型方法。

二、爲什麼要使用泛型

泛型在開發過程中經常出現,比如我們一直高頻使用的List集合,我們可以這麼創建一個 ArrayList 集合。

List<String> stringList = new ArrayList<>();

再看一下List的定義

public interface List<E> extends Collection<E>{...}

我們看到在List接口後面加了 <E> 對類型進行參數化,及參數是可變的,需要我們在使用的時候傳入實際的參數,這樣的好處是什麼?

我們可以看看這麼一個例子:

當我們對List 添加泛型後,我們在使用的時候,可以根據需求聲明不同類型的實際參數,如果我們傳入的String,我們對List集合添加數據的時候,很明顯看到當添加的數據不是String類型的時候,編譯器會報錯,當我們在獲取數據的時候,也不需要強制類型轉換,會自動返回我們傳入實際參數的類型。

如果我們不傳入實際類型,會出現什麼情況?

我們定義的List集合沒有指定明確的參數,在數據添加的時候,可以看到我們可以添加不同類型的參數,編譯器也沒有報錯,在獲取數據的時候,返回的是Object類型,所以我們需要對其進行強制轉換,而這個過程,很容易就會報ClassCastException異常。

所以,我們使用泛型的好處有:

1、適用於多種數據類型執行相同的代碼

2、類型安全:我們使用泛型的時候,指定了特定的參數類型,這樣對其類型進行限定,可以在編譯期間對我們的傳入的參數類型進行判斷,增加了類型的安全性。

3、取消強制類型轉換:我們指定明確的類型參數後,由於在編譯階段就會對類型進行約束,泛型會自動且隱式的給我們做類型轉換,轉換成我們指定的類型,我們不再需要關心類型的轉換。

三、泛型的使用

泛型可以定義在類、接口、方法上,分別被稱爲泛型類、泛型接口、泛型方法。

3.1、泛型類

通過 <> 將類型變量T(大寫字母都可以,不過常用的就是T,E,K,V等等)括起來,放在類名後面,泛型類運行有多個類型變量。

一個類型變量的泛型類:

/**
 * @author : EvanZch
 *         description: 一個類型變量的泛型類
 **/
public class genericClassTest<T> {
    private T mData;

    public genericClassTest() {
    }

    public T getmData() {
        return mData;
    }

    public void setmData(T mData) {
        this.mData = mData;
    }
}

多個類型變量的泛型類:

/**
 * @author : EvanZch
 * description: 兩個類型變量的泛型類
 **/
public class genericClassTest1<T, K> {
  // ...
}

3.2、泛型接口

泛型接口的定義和泛型一致

/**
 * @author : EvanZch
 *         description: 泛型接口
 **/
public interface genericInterface<T> {

    T getData();

    void set(T data);
}

我們在實現泛型接口可以使用下面兩種方式

1、不指定具體的泛型實參

/**
 * @author : EvanZch
 *         description:實現泛型接口,不指定具體類型
 **/
public class genericInterfaceImpl1<T> implements genericInterface<T> {

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

    @Override
    public void set(T data) {

    }
}

// 我們在調用的時候,再傳入實際類型
genericInterfaceImpl1<String> interfaceImpl1 = new genericInterfaceImpl1();

2、指定具體的泛型實參

/**
 * @author : EvanZch
 *         description:實現泛型接口,指定具體類型
 **/
public class genericInterfaceImpl2  implements genericInterface<String> {

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

    @Override
    public void set(String data) {

    }
}

// 使用時候,直接創建
genericInterfaceImpl2 genericInterfaceImpl2 = new genericInterfaceImpl2();

3.3、泛型方法

泛型方法,是在調用方法的時候指明泛型的具體類型 ,泛型方法可以在任何地方和任何場景中使用,包括普通類泛型類。注意泛型類中定義的普通方法和泛型方法的區別。

我們區別一下普通的方法和泛型方法

我們看到普通方法中也使用了泛型,但是它只是一個普通的方法,只是它的返回值和傳入的類型是在前面已經聲明過得泛型,所以,這裏纔可以繼續使用 T 這個類型變量。

而下面這個泛型方法,首先通過 <E> 標識了它是一個泛型方法,返回值類型和傳入的類型一致,通過泛型進行參數化了。

四、泛型類型變量的限制

我們使用泛型的時候,對類型進行參數化,使用的可以傳入任意的類型,但是在實際使用過程中,我們可能需要對類型進行限制,在進行類型擦除的時候,會轉換成我們限定的類型,比如我們要比較兩個字符的大小,需要用到 compareTo 方法

int compareTo(Object o) 或 int compareTo(String anotherString)

返回值是整型,它是先比較對應字符的大小(ASCII碼順序),如果第一個字符和參數的第一個字符不等,結束比較,返回他們之間的差值,如果第一個字符和參數的第一個字符相等,則以第二個字符和參數的第二個字符做比較,以此類推,直至比較的字符或被比較的字符有一方結束。

  • 如果參數字符串等於此字符串,則返回值 0;
  • 如果此字符串小於字符串參數,則返回一個小於 0 的值;
  • 如果此字符串大於字符串參數,則返回一個大於 0 的值。

可以看到如果我們直接這麼定義,會報錯,因爲T爲任意類型,但是 compareTo 方法定義在Comparable 接口中,我們需要限定傳入的類型必須實現了 Comparable 接口

public interface Comparable<T> {
    public int compareTo(T o);
}

我們可以這麼寫:

這裏我們對類型對進行了限定,使用了通配符和指明瞭上界 ? extends X ,這樣就限制了我們傳入的參數類型必須是實現了Comparable接口,我們在調用的時候,編譯器就會進行判斷,我們傳入的參數是否實現了Comparable接口,如果沒有就會報錯。

Integer 實現了 Comparable 接口

public final class Integer extends Number implements Comparable

五、泛型通配符

上面類型變量的限制中,我們使用了通配符 ? ,通配符的有三種使用方式。
<? extends X> : 類型的上界限定,參數類型是X的子類。
<? super X> :類型的下界限定,參數類型是X的超類。

<?> : 無界限定。

爲了說明他們的區別,我們先新建People類、Man類、Son類,其中Man繼承至PeopleSon 繼承至 Man

People類:

public class People {
}

Man 類:

public class Man extends People {
}

Son 類:

public class Son extends Man {
}

再創建一個泛型類 Test<T>

public class Test<T> {
    
    private T data;

    public T getData() {
        return data;
    }

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

查看繼承關係:

很明顯,我們可以看到People類和Man類是具有繼承關係的,但是 Test<People>Test<Man> 之間卻沒有任何關係。

我們進行如下操作

可以看到set,get都沒問題,因爲我們將泛型直接指定爲 People,而 ManSon 都是people的子類,所以我們都能set進去。

1、? extends X : 上界限定通配符

我在再使用 ? extends X ,指明類型的上界限定爲 X,表示傳入方法的類型參數必須是其本身或子類。

但是對於泛型類來說,如果內部提供了get\set方法,那麼set不允許調用,即類型的上界只讀。

我們可以看到,我們通過 <? extends People> 指明瞭類型參數上界爲 People,那我們執行取操作的時候,即調用get的時候,編譯器知道返回的肯定是個 x(不管是x還是x的子類),但是我們進行設置的時候,編譯器只知道我們傳入的是 x ,至於具體是 x 的那個子類並不知道,所以沒有辦法進行set操作。

總結:上不存。

主要用於安全地訪問數據,可以訪問X及其子類型,並且不能寫入非null的數據。

2、? super X : 下界限定通配符

使用 ? super X ,指明類型的下界限定爲 X,表示傳入方法的類型參數必須是其本身或其超類。

但是對於泛型類來說,如果內部提供了get\set方法,那麼set只能傳入X的子類及其本身,get返回的類型是Object。

? super X 表示類型的下界,類型參數是X的超類(包括X本身),那麼可以肯定的說,get方法返回的一定是個X的超類,那麼到底是哪個超類?不知道,但是可以肯定的說,Object一定是它的超類,所以get方法返回Object。編譯器是可以確定知道的。對於set方法來說,編譯器不知道它需要的確切類型,但是X和X的子類可以安全的轉型爲X。

總結:下不取

主要用於安全地寫入數據,可以寫入X及其子類型。

3、<?> 無限定通配符

表示類型無限制,和 T 的區別

ArrayList al=new ArrayList(); 指定集合元素只能是T類型

ArrayList<?> al=new ArrayList<?>(); 集合元素可以是任意類型,這種沒有意義,一般是方法中,只是爲了說明用法。

總結

泛型在java語言中的一種語法糖的存在,java中泛型的實現爲類型擦除,是一種僞泛型。

發佈了15 篇原創文章 · 獲贊 4 · 訪問量 9896
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章