一、什麼是泛型
泛型就是參數化類型,即我們在定義的時候,將具體的類型進行參數化,在調用或者使用的時候,再傳入具體的參數類型,我們可以將泛型用在類、接口和方法中,分別被稱爲泛型類、泛型接口、泛型方法。
二、爲什麼要使用泛型
泛型在開發過程中經常出現,比如我們一直高頻使用的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
繼承至People
,Son
繼承至 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
,而 Man
和 Son
都是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中泛型的實現爲類型擦除,是一種僞泛型。