Java 泛型
本小節(jié)我們將學習 Java5 以后出現(xiàn)的一個特性:泛型(Generics
)。通過本小節(jié)的學習,你將了解到什么是泛型,為什么需要泛型,如何使用泛型,如何自定義泛型,類型通配符等知識。
1. 什么是泛型
泛型不只是 Java 語言所特有的特性,泛型是程序設計語言的一種特性。允許程序員在強類型的程序設計語言中編寫代碼時定義一些可變部分,那些部分在使用前必須做出聲明。
我們在上一小節(jié)已經(jīng)了解到,Java 中的集合類是支持泛型的,它在代碼中是這個樣子的:

代碼中的<Integer>
就是泛型,我們把類型像參數(shù)一樣傳遞,尖括號中間就是數(shù)據(jù)類型,我們可以稱之為實際類型參數(shù),這里實際類型參數(shù)的數(shù)據(jù)類型只能為引用數(shù)據(jù)類型。
那么為什么需要泛型呢?我們馬上就見分曉。
2. 為什么需要泛型
上一節(jié)中,我們在使用ArrayList
實現(xiàn)類的時候,如果沒有指定泛型,IDEA
會給出警告,代碼似乎也是可以順利運行的。請看如下實例:
import java.util.ArrayList;
public class GenericsDemo1 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("Hello");
String str = (String) arrayList.get(0);
System.out.println("str=" + str);
}
}
運行結(jié)果:
str=Hello
雖然運行時沒有發(fā)生任何異常,但這樣做有兩個缺點:
- 需要強制類型轉(zhuǎn)換: 由于
ArrayList
內(nèi)部就是一個Object[]
數(shù)組,在get()
元素的時候,返回的是Object
類型,所以在ArrayList
外獲取該對象,需要強制類型轉(zhuǎn)換。其它的Collection
、Map
如果不使用泛型,也存在這個問題; - 可向集合中添加任意類型的對象,存在類型不安全風險。例如如下代碼中,我們向列表中既添加了
Integer
類型,又添加了String
類型:
import java.util.ArrayList;
public class GenericsDemo2 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add(123);
arrayList.add("Hello");
String str = (String) arrayList.get(0);
System.out.println("element=" + str);
}
}
運行結(jié)果:
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
at GenericsDemo2.main(GenericsDemo2.java:8)
由于我們的“疏忽”,列表第 1 個元素實際上是整型,但被我們強制轉(zhuǎn)換為字符串類型,這是行不通的,因此會拋出ClassCastException
異常。
使用泛型可以解決這些問題。泛型有如下優(yōu)點:
- 可以減少類型轉(zhuǎn)換的次數(shù),代碼更加簡潔;
- 程序更加健壯:只要編譯期沒有警告,運行期就不會拋出
ClassCastException
異常; - 提高了代碼的可讀性:編寫集合的時候,就限定了集合中能存放的類型。
3. 如何使用泛型
3.1 泛型使用
在代碼中,這樣使用泛型:
List<String> list = new ArrayList<String>();
// Java 7 及以后的版本中,構(gòu)造方法中可以省略泛型類型:
List<String> list = new ArrayList<>();
要注意的是,變量聲明的類型必須與傳遞給實際對象的類型保持一致,下面是錯誤的例子:
List<Object> list = new ArrayList<String>();
List<Number> numbers = new ArrayList(Integer);
3.2 自定義泛型類
3.2.1 Java 源碼中泛型的定義
在自定義泛型類之前,我們來看下java.util.ArrayList
是如何定義的:

類名后面的<E>
就是泛型的定義,E
不是 Java 中的一個具體的類型,它是 Java 泛型的通配符(注意是大寫的,實際上就是Element
的含義),可將其理解為一個占位符,將其定義在類上,使用時才確定類型。此處的命名不受限制,但最好有一定含義,例如java.lang.HashMap
的泛型定義為HashMap<K,V>
,K
表示Key
,V
表示Value
。
3.2.2 自定義泛型類實例1
下面我們來自定義一個泛型類,自定義泛型按照約定俗成可以叫<T>
,具有Type
的含義,實例如下:
public class NumberGeneric<T> { // 把泛型定義在類上
private T number; // 定義在類上的泛型,在類內(nèi)部可以使用
public T getNumber() {
return number;
}
public void setNumber(T number) {
this.number = number;
}
public static void main(String[] args) {
// 實例化對象,指定元素類型為整型
NumberGeneric<Integer> integerNumberGeneric = new NumberGeneric<>();
// 分別調(diào)用set、get方法
integerNumberGeneric.setNumber(123);
System.out.println("integerNumber=" + integerNumberGeneric.getNumber());
// 實例化對象,指定元素類型為長整型
NumberGeneric<Long> longNumberGeneric = new NumberGeneric<>();
// 分別調(diào)用set、get方法
longNumberGeneric.setNumber(20L);
System.out.println("longNumber=" + longNumberGeneric.getNumber());
// 實例化對象,指定元素類型為雙精度浮點型
NumberGeneric<Double> doubleNumberGeneric = new NumberGeneric<>();
// 分別調(diào)用set、get方法
doubleNumberGeneric.setNumber(4000.0);
System.out.println("doubleNumber=" + doubleNumberGeneric.getNumber());
}
}
運行結(jié)果:
integerNumber=123
longNumber=20
doubleNumber=4000.0
我們在類的定義處也定義了泛型:NumberGeneric<T>
;在類內(nèi)部定義了一個T
類型的number
變量,并且為其添加了setter
和getter
方法。
對于泛型類的使用也很簡單,在主方法中,創(chuàng)建對象的時候指定T
的類型分別為Integer
、Long
、Double
,類就可以自動轉(zhuǎn)換成對應的類型了。
3.2.3 自定義泛型類實例2
上面我們知道了如何定義含有單個泛型的類,那么對于含有多個泛型的類,如何定義呢?
我們可以看一下HashMap
類是如何定義的。如下是 Java 源碼的截圖:

參照HashMap<K,V>
類的定義,下面我們來看看如何定義含有兩個泛型的類,實例如下:
public class KeyValueGeneric<K,V> { // 把兩個泛型K、V定義在類上
/**
* 類型為K的key屬性
*/
private K key;
/**
* 類型為V的value屬性
*/
private V value;
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public static void main(String[] args) {
// 實例化對象,分別指定元素類型為整型、長整型
KeyValueGeneric<Integer, Long> integerLongKeyValueGeneric = new KeyValueGeneric<>();
// 調(diào)用setter、getter方法
integerLongKeyValueGeneric.setKey(200);
integerLongKeyValueGeneric.setValue(300L);
System.out.println("key=" + integerLongKeyValueGeneric.getKey());
System.out.println("value=" + integerLongKeyValueGeneric.getValue());
// 實例化對象,分別指定元素類型為浮點型、字符串類型
KeyValueGeneric<Float, String> floatStringKeyValueGeneric = new KeyValueGeneric<>();
// 調(diào)用setter、getter方法
floatStringKeyValueGeneric.setKey(0.5f);
floatStringKeyValueGeneric.setValue("零點五");
System.out.println("key=" + floatStringKeyValueGeneric.getKey());
System.out.println("value=" + floatStringKeyValueGeneric.getValue());
}
}
運行結(jié)果:
key=200
value=300
key=0.5
value=零點五
3.3 自定義泛型方法
前面我們知道了如何定義泛型類,在類上定義的泛型,在方法中也可以使用。下面我們來看一下如何自定義泛型方法。
泛型方法不一定寫在泛型類當中。當類的調(diào)用者總是關(guān)心類中的某個泛型方法,不關(guān)心其他屬性,這個時候就沒必要再整個類上定義泛型了。
請查看如下實例:
public class GenericMethod {
/**
* 泛型方法show
* @param t 要打印的參數(shù)
* @param <T> T
*/
public <T> void show(T t) {
System.out.println(t);
}
public static void main(String[] args) {
// 實例化對象
GenericMethod genericMethod = new GenericMethod();
// 調(diào)用泛型方法show,傳入不同類型的參數(shù)
genericMethod.show("Java");
genericMethod.show(222);
genericMethod.show(222.0);
genericMethod.show(222L);
}
}
運行結(jié)果:
Java
222
222.0
222
實例中,使用<T>
來定義show
方法的泛型,它接收一個泛型的參數(shù)變量并在方法體打?。徽{(diào)用泛型方法也很簡單,在主方法中實例化對象,調(diào)用對象下的泛型方法,可傳入不同類型的參數(shù)。
4. 泛型類的子類
泛型類也是一個 Java 類,它也具有繼承的特性。
泛型類的繼承可分為兩種情況:
- 子類明確泛型類的類型參數(shù)變量;
- 子類不明確泛型類的類型參數(shù)變量。
下面我們來分別看一下這兩種情況。
4.1 明確類型參數(shù)變量
例如,有一個泛型接口:
public interface GenericInterface<T> { // 在接口上定義泛型
void show(T t);
}
泛型接口的實現(xiàn)類如下:
public class GenericInterfaceImpl implements GenericInterface<String> { // 明確泛型類型為String類型
@Override
public void show(String s) {
System.out.println(s);
}
}
子類實現(xiàn)明確了泛型的參數(shù)變量為String
類型。因此方法show()
的重寫也將T
替換為了String
類型。
4.2 不明確類型參數(shù)變量
當實現(xiàn)類不確定泛型類的參數(shù)變量時,實現(xiàn)類需要定義類型參數(shù)變量,調(diào)用者使用子類時,也需要傳遞類型參數(shù)變量。
如下是GenericInterface
接口的另一個實現(xiàn)類:
public class GenericInterfaceImpl1<T> implements GenericInterface<T> { // 實現(xiàn)類也需要定義泛型參數(shù)變量
@Override
public void show(T t) {
System.out.println(t);
}
}
在主方法中調(diào)用實現(xiàn)類的show()
方法:
public static void main(String[] args) {
GenericInterfaceImpl1<Float> floatGenericInterfaceImpl1 = new GenericInterfaceImpl1<>();
floatGenericInterfaceImpl1.show(100.1f);
}
5. 類型通配符
我們先來看一個泛型作為方法參數(shù)的實例:
import java.util.ArrayList;
import java.util.List;
public class GenericDemo3 {
/**
* 遍歷并打印集合中的每一個元素
* @param list 要接收的集合
*/
public void printListElement(List<Object> list) {
for (Object o : list) {
System.out.println(o);
}
}
}
觀察上面的代碼,參數(shù)list
的限定的泛型類型為Object
, 也就是說,這個方法只能接收元素為Object
類型的集合,如果我們想傳遞其他元素類型的集合,是行不通的。例如,如果傳遞裝載Integer
元素的集合,程序在編譯階段就會報錯:

Tips: 泛型中的
List<Object>
并不是List<Integer>
的父類,它們不滿足繼承關(guān)系。
5.1 無限定通配符
想要解決這個問題,使用類型通配符即可,修改方法參數(shù)處的代碼,將<>
中間的Object改為?
即可:
public void printListElement(List<?> list) {
此處的?
就是類型通配符,表示可以匹配任意類型,因此調(diào)用方可以傳遞任意泛型類型的列表。
完整實例如下:
import java.util.ArrayList;
import java.util.List;
public class GenericDemo3 {
/**
* 遍歷并打印集合中的每一個元素
* @param list 要接收的集合
*/
public void printListElement(List<?> list) {
for (Object o : list) {
System.out.println(o);
}
}
public static void main(String[] args) {
// 實例化一個整型的列表
List<Integer> integers = new ArrayList<>();
// 添加元素
integers.add(1);
integers.add(2);
integers.add(3);
GenericDemo3 genericDemo3 = new GenericDemo3();
// 調(diào)用printListElement()方法
genericDemo3.printListElement(integers);
// 實例化一個字符串類型的列表
List<String> strings = new ArrayList<>();
// 添加元素
strings.add("Hello");
strings.add("慕課網(wǎng)");
// 調(diào)用printListElement()方法
genericDemo3.printListElement(strings);
}
}
運行結(jié)果:
1
2
3
Hello
慕課網(wǎng)
5.2 extends 通配符
extends
通配符用來限定泛型的上限。什么意思呢?依舊以上面的實例為例,我們來看一個新的需求,我們希望方法接收的List
集合限定在數(shù)值類型內(nèi)(float、integer、double、byte 等),不希望其他類型可以傳入(比如字符串)。此時,可以改寫上面的方法定義,設定上界通配符:
public void printListElement(List<? extends Number> list) {
這樣的寫法的含義為:List
集合裝載的元素只能是Number
自身或其子類(Number
類型是所有數(shù)值類型的父類),完整實例如下:
import java.util.ArrayList;
import java.util.List;
public class GenericDemo4 {
/**
* 遍歷并打印集合中的每一個元素
* @param list 要接收的集合
*/
public void printListElement(List<? extends Number> list) {
for (Object o : list) {
System.out.println(o);
}
}
public static void main(String[] args) {
// 實例化一個整型的列表
List<Integer> integers = new ArrayList<>();
// 添加元素
integers.add(1);
integers.add(2);
integers.add(3);
GenericDemo4 genericDemo3 = new GenericDemo4();
// 調(diào)用printListElement()方法
genericDemo3.printListElement(integers);
}
}
運行結(jié)果:
1
2
3
5.3 super 通配符
既然已經(jīng)了解了如何設定通配符上界,也就不難理解通配符的下界了,可以限定傳遞的參數(shù)只能是某個類型的父類。
語法如下:
<? super Type>
6. 小結(jié)
通過本小節(jié)的學習,我們知道了使用泛型可以避免強制類型轉(zhuǎn)換,也可以避免運行期就拋出的ClassCastException
異常。在使用泛型時,要注意變量聲明的泛型類型要匹配傳遞給實際對象的類型, Java 7 及以后的版本中,構(gòu)造方法中可以省略泛型類型,推薦直接省略。
我們也學習了如何自定義泛型類和泛型方法,在實際的開發(fā)中,我們想要編寫比較通用的代碼就避免不了使用泛型,大家可以在以后的開發(fā)中慢慢體悟。
另外,泛型也是可以繼承的。
最后,我們還講解了類型通配符的概念和使用場景。