Java SE 泛型原理、語法與實踐指南
泛型(Generics)是 Java SE 5 引入的核心特性,其本質(zhì)是 “參數(shù)化類型”—— 允許在定義類、接口、方法時指定 “類型參數(shù)”,在使用時再明確具體類型(如List<String>、Map<Integer, User>)。它像 “類型的占位符”,既能保證編譯時的類型安全(避免ClassCastException),又能減少重復(fù)代碼(實現(xiàn)通用邏輯復(fù)用),是集合框架、工具類、框架開發(fā)的基礎(chǔ)。
一、泛型的核心價值:為什么需要泛型?
在泛型出現(xiàn)之前,Java 通過Object類型實現(xiàn) “通用” 邏輯,但存在類型不安全和代碼冗余兩大問題。泛型的核心目標(biāo)就是解決這兩個痛點。
1. 無泛型的痛點:以集合為例
(1)類型不安全(運行時拋異常)
ArrayList在 JDK 1.4 及以前的實現(xiàn)中,內(nèi)部用Object[]存儲元素,添加元素時不限制類型,取出時需強制轉(zhuǎn)換,若類型不匹配,編譯時無提示,運行時拋ClassCastException:
// 無泛型:ArrayList存儲Object,可添加任意類型
import java.util.ArrayList;
public class NoGenericDemo {
public static void main(String\[] args) {
ArrayList list = new ArrayList();
list.add("Java"); // 添加String
list.add(123); // 添加Integer(編譯不報錯,隱藏風(fēng)險)
list.add(new Object()); // 添加Object
// 取出元素:需強制轉(zhuǎn)換為String,運行時出錯
for (int i = 0; i < list.size(); i++) {
String str = (String) list.get(i); // 第2個元素是Integer,運行拋ClassCastException
System.out.println(str.length());
}
}
}問題本質(zhì):編譯階段無法校驗元素類型,錯誤延遲到運行時,風(fēng)險不可控。
(2)代碼冗余(重復(fù)強制轉(zhuǎn)換)
即使存儲的是同一類型,每次取出都需強制轉(zhuǎn)換,代碼繁瑣:
ArrayList list = new ArrayList();
list.add("Apple");
list.add("Banana");
// 每次取出都需強制轉(zhuǎn)換為String,冗余且易出錯
String apple = (String) list.get(0);
String banana = (String) list.get(1);2. 泛型的解決方案:類型參數(shù)化
通過泛型指定 “元素類型”,編譯階段即可校驗類型合法性,避免強制轉(zhuǎn)換:
// 有泛型:ArrayList\<String>指定元素只能是String
import java.util.ArrayList;
public class GenericDemo {
public static void main(String\[] args) {
ArrayList\<String> list = new ArrayList<>(); // 菱形語法,JDK 7+可省略右側(cè)類型
list.add("Java"); // 合法:String類型
// list.add(123); // 編譯報錯:不允許添加Integer,類型安全校驗
// list.add(new Object()); // 編譯報錯:類型不匹配
// 取出元素:無需強制轉(zhuǎn)換,直接是String類型
for (String str : list) {
System.out.println(str.length()); // 安全調(diào)用String方法
}
}
}3. 泛型的核心價值總結(jié)
| 核心價值 | 說明 | 示例 |
|---|---|---|
| 編譯時類型安全 | 限制集合 / 對象的元素類型,不允許添加不匹配類型,提前暴露錯誤 | ArrayList<String>不允許添加 Integer |
| 消除強制轉(zhuǎn)換 | 使用時無需手動轉(zhuǎn)換類型,代碼簡潔且避免ClassCastException | String str = list.get(0)(無需強轉(zhuǎn)) |
| 代碼復(fù)用 | 一套通用邏輯適配多種類型,無需為每種類型寫重復(fù)代碼(如泛型工具類) | GenericUtil.swap<T>(T[] arr, int i, int j)適配所有數(shù)組類型 |
| 清晰的代碼意圖 | 類型參數(shù)明確告知數(shù)據(jù)類型,代碼可讀性更高 | Map<Long, User>明確鍵是 Long,值是 User |
二、泛型的基本語法:類、接口、方法
泛型的使用場景分為三類:泛型類(含枚舉)、泛型接口、泛型方法,每種場景的語法略有差異,但核心都是 “定義類型參數(shù),使用時指定具體類型”。
1. 泛型類:類定義時指定類型參數(shù)
泛型類是 “包含類型參數(shù)的類”,在創(chuàng)建對象時需明確具體類型(如List<String>、HashMap<K, V>)。
(1)語法格式
// 定義泛型類:\<T1, T2, ...>中T1、T2是類型參數(shù)(占位符)
class 類名\<T1, T2, ...> {
// 1. 用類型參數(shù)定義成員變量
private T1 field1;
private T2 field2;
// 2. 用類型參數(shù)定義構(gòu)造方法
public 類名(T1 field1, T2 field2) {
this.field1 = field1;
this.field2 = field2;
}
// 3. 用類型參數(shù)定義方法的返回值或參數(shù)
public T1 getField1() {
return field1;
}
public void setField2(T2 field2) {
this.field2 = field2;
}
}(2)類型參數(shù)命名規(guī)范(約定俗成)
為提高可讀性,類型參數(shù)通常用單個大寫字母表示,常見約定:
T:Type(通用類型,最常用);E:Element(集合元素類型,如List<E>);K:Key(鍵類型,如Map<K, V>);V:Value(值類型,如Map<K, V>);N:Number(數(shù)值類型,如Integer、Double);S、U、V:多個類型參數(shù)時的后續(xù)占位符(如Class<S, U>)。
(3)代碼示例:自定義泛型類(Pair)
// 泛型類:存儲一對不同類型的值(如鍵值對、名稱-年齡等)
class Pair\<K, V> {
private K key;
private V value;
// 泛型構(gòu)造方法
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
// 泛型方法(返回值為類型參數(shù))
public K getKey() {
return key;
}
public V getValue() {
return value;
}
// 泛型方法(參數(shù)為類型參數(shù))
public void setValue(V value) {
this.value = value;
}
// 普通方法:打印鍵值對
@Override
public String toString() {
return "Pair{" + "key=" + key + ", value=" + value + "}";
}
}
// 使用泛型類
public class GenericClassDemo {
public static void main(String\[] args) {
// 1. 創(chuàng)建Pair\<String, Integer>對象:key是String,value是Integer
Pair\<String, Integer> userPair = new Pair<>("張三", 20);
String userName = userPair.getKey(); // 無需強轉(zhuǎn),直接是String
Integer userAge = userPair.getValue(); // 無需強轉(zhuǎn),直接是Integer
System.out.println(userPair); // 輸出:Pair{key=張三, value=20}
// 2. 創(chuàng)建Pair\<Long, User>對象:key是Long,value是自定義User類
User user = new User(1001, "李四");
Pair\<Long, User> userInfoPair = new Pair<>(1001L, user);
Long userId = userInfoPair.getKey();
User userInfo = userInfoPair.getValue();
System.out.println(userInfoPair); // 輸出:Pair{key=1001, value=User{id=1001, name='李四'}}
// 3. 錯誤示例:添加不匹配類型
// userPair.setValue("25"); // 編譯報錯:需傳入Integer類型,不能傳String
}
}
// 自定義User類(用于示例)
class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" + "id=" + id + ", name='" + name + "'}";
}
}2. 泛型接口:接口定義時指定類型參數(shù)
泛型接口與泛型類類似,在定義接口時指定類型參數(shù),實現(xiàn)接口時需明確具體類型(或繼續(xù)保留泛型)。
(1)語法格式
// 定義泛型接口
interface 接口名\<T1, T2, ...> {
// 1. 用類型參數(shù)定義抽象方法的返回值或參數(shù)
T1 method1(T2 param);
// 2. 用類型參數(shù)定義常量(JDK 1.8+允許接口有默認(rèn)方法和靜態(tài)方法)
default void method2(T1 param) {
// 默認(rèn)方法實現(xiàn)
}
}(2)代碼示例:自定義泛型接口(Converter)
// 泛型接口:定義“類型轉(zhuǎn)換”行為,T是源類型,R是目標(biāo)類型
interface Converter\<T, R> {
// 抽象方法:將T類型轉(zhuǎn)換為R類型
R convert(T source);
// 默認(rèn)方法:批量轉(zhuǎn)換(JDK 1.8+)
default List\<R> convertList(List\<T> sourceList) {
List\<R> resultList = new ArrayList<>();
for (T source : sourceList) {
resultList.add(convert(source));
}
return resultList;
}
}
// 實現(xiàn)泛型接口:String→Integer轉(zhuǎn)換器
class StringToIntegerConverter implements Converter\<String, Integer> {
@Override
public Integer convert(String source) {
// 實現(xiàn)String到Integer的轉(zhuǎn)換(處理空值)
return source == null ? 0 : Integer.parseInt(source.trim());
}
}
// 測試泛型接口
public class GenericInterfaceDemo {
public static void main(String\[] args) {
// 1. 創(chuàng)建轉(zhuǎn)換器實例
Converter\<String, Integer> converter = new StringToIntegerConverter();
// 2. 單個轉(zhuǎn)換
Integer num = converter.convert("123");
System.out.println("單個轉(zhuǎn)換結(jié)果:" + num); // 輸出:123
// 3. 批量轉(zhuǎn)換(調(diào)用默認(rèn)方法)
List\<String> strList = Arrays.asList("456", "789", "1000");
List\<Integer> intList = converter.convertList(strList);
System.out.println("批量轉(zhuǎn)換結(jié)果:" + intList); // 輸出:\[456, 789, 1000]
// 4. 其他實現(xiàn):如Integer→String轉(zhuǎn)換器(匿名內(nèi)部類)
Converter\<Integer, String> intToStringConverter = new Converter\<Integer, String>() {
@Override
public String convert(Integer source) {
return source == null ? "0" : "數(shù)值:" + source;
}
};
String str = intToStringConverter.convert(5);
System.out.println("Integer→String轉(zhuǎn)換:" + str); // 輸出:數(shù)值:5
}
}3. 泛型方法:方法定義時指定類型參數(shù)
泛型方法是 “包含類型參數(shù)的方法”,獨立于泛型類 / 接口—— 即使在普通類中,也可定義泛型方法,調(diào)用時需明確類型(或由編譯器自動推斷)。
(1)語法格式
// 泛型方法:\<T1, T2, ...>放在返回值前,是方法的類型參數(shù)
修飾符 \<T1, T2, ...> 返回值類型 方法名(T1 param1, T2 param2, ...) {
// 方法體:使用類型參數(shù)
}(2)關(guān)鍵區(qū)別:泛型方法 vs 泛型類的方法
- 泛型類的方法:類型參數(shù)屬于類,創(chuàng)建類對象時確定類型(如
Pair<K, V>的getKey()方法,K 的類型在new Pair<>()時確定); - 泛型方法:類型參數(shù)屬于方法,調(diào)用方法時確定類型(或編譯器推斷),與類是否泛型無關(guān)(普通類也可定義泛型方法)。
(3)代碼示例:自定義泛型工具方法
import java.util.Arrays;
// 普通類(非泛型類)中定義泛型方法
class GenericUtil {
// 泛型方法1:交換數(shù)組中兩個位置的元素(適配所有類型的數(shù)組)
public static \<T> void swap(T\[] arr, int i, int j) {
// 校驗參數(shù)合法性
if (arr == null || i < 0 || j < 0 || i >= arr.length || j >= arr.length) {
throw new IllegalArgumentException("參數(shù)非法!");
}
T temp = arr\[i];
arr\[i] = arr\[j];
arr\[j] = temp;
}
// 泛型方法2:獲取數(shù)組中的第一個元素(適配所有類型的數(shù)組)
public static \<T> T getFirstElement(T\[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
return arr\[0];
}
// 泛型方法3:帶邊界的泛型方法(后續(xù)“泛型邊界”章節(jié)詳解)
public static \<T extends Comparable\<T>> T getMax(T\[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
T max = arr\[0];
for (T element : arr) {
if (element.compareTo(max) > 0) { // 調(diào)用Comparable接口方法
max = element;
}
}
return max;
}
}
// 測試泛型方法
public class GenericMethodDemo {
public static void main(String\[] args) {
// 1. 交換String數(shù)組元素(編譯器自動推斷T為String)
String\[] strArr = {"A", "B", "C", "D"};
GenericUtil.swap(strArr, 1, 3); // 交換索引1和3的元素
System.out.println("交換后String數(shù)組:" + Arrays.toString(strArr)); // 輸出:\[A, D, C, B]
// 2. 交換Integer數(shù)組元素(顯式指定T為Integer,也可省略)
Integer\[] intArr = {1, 2, 3, 4};
GenericUtil.\<Integer>swap(intArr, 0, 2); // 顯式指定類型
System.out.println("交換后Integer數(shù)組:" + Arrays.toString(intArr)); // 輸出:\[3, 2, 1, 4]
// 3. 獲取數(shù)組第一個元素
Integer firstInt = GenericUtil.getFirstElement(intArr);
System.out.println("Integer數(shù)組第一個元素:" + firstInt); // 輸出:3
// 4. 獲取數(shù)組最大值(T需實現(xiàn)Comparable接口)
Integer\[] numArr = {5, 2, 9, 1};
Integer maxNum = GenericUtil.getMax(numArr);
System.out.println("數(shù)組最大值:" + maxNum); // 輸出:9
// 錯誤示例:非Comparable類型無法調(diào)用getMax(編譯報錯)
// User\[] userArr = {new User(1L, "張三"), new User(2L, "李四")};
// GenericUtil.getMax(userArr); // 編譯報錯:User未實現(xiàn)Comparable\<User>
}
}三、泛型的核心特性:類型擦除(Type Erasure)
Java 的泛型是 “編譯時泛型”—— 編譯階段會將泛型的 “類型參數(shù)” 擦除為 “原始類型”(Raw Type),運行時 JVM 不感知泛型類型,僅保留原始類型信息。這是 Java 泛型與 C++ 模板的核心區(qū)別(C++ 模板在編譯時生成不同類型的代碼)。
1. 類型擦除的規(guī)則
(1)無邊界泛型:擦除為Object
若泛型類型參數(shù)無顯式上界(如T),編譯后擦除為Object:
// 泛型類:無邊界
class Box\<T> {
private T value;
public T getValue() { return value; }
public void setValue(T value) { this.value = value; }
}
// 編譯后擦除為原始類型(偽代碼)
class Box {
private Object value;
public Object getValue() { return value; }
public void setValue(Object value) { this.value = value; }
}(2)有上界泛型:擦除為 “上界類型”
若泛型類型參數(shù)有顯式上界(如T extends Number),編譯后擦除為上界類型(Number):
// 泛型類:有上界(T必須是Number的子類)
class NumberBox\<T extends Number> {
private T value;
public T getValue() { return value; }
public void setValue(T value) { this.value = value; }
}
// 編譯后擦除為原始類型(偽代碼)
class NumberBox {
private Number value;
public Number getValue() { return value; }
public void setValue(Number value) { this.value = value; }
}(3)泛型方法的擦除
泛型方法的類型參數(shù)同樣會擦除,無邊界擦除為Object,有上界擦除為上界類型:
// 泛型方法:無邊界
public static \<T> T getFirst(T\[] arr) { ... }
// 編譯后擦除為(偽代碼)
public static Object getFirst(Object\[] arr) { ... }
// 泛型方法:有上界
public static \<T extends Comparable\<T>> T getMax(T\[] arr) { ... }
// 編譯后擦除為(偽代碼)
public static Comparable getMax(Comparable\[] arr) { ... }2. 類型擦除的影響:橋方法(Bridge Method)
類型擦除可能導(dǎo)致 “重寫方法簽名不匹配”,JVM 會自動生成 “橋方法” 解決此問題。
代碼示例:橋方法的生成
// 泛型接口:Comparable\<T>(JDK自帶)
interface Comparable\<T> {
int compareTo(T o);
}
// 實現(xiàn)類:String實現(xiàn)Comparable\<String>
class String implements Comparable\<String> {
// 實現(xiàn)compareTo方法:參數(shù)是String
@Override
public int compareTo(String anotherString) {
// 字符串比較邏輯
return this.equals(anotherString) ? 0 : 1; // 簡化邏輯
}
}
// 類型擦除后:
// Comparable接口的compareTo方法擦除為int compareTo(Object o)
// String的compareTo方法簽名是int compareTo(String o),與擦除后的接口方法不匹配
// JVM自動生成橋方法(偽代碼):
class String implements Comparable {
// 1. 橋方法:匹配擦除后的接口方法
public int compareTo(Object o) {
// 調(diào)用實際的compareTo(String)方法
return compareTo((String) o);
}
// 2. 實際實現(xiàn)的方法
public int compareTo(String anotherString) {
return this.equals(anotherString) ? 0 : 1;
}
}橋方法的作用:確保類型擦除后,子類仍能正確重寫父類 / 接口的方法,避免多態(tài)失效。
3. 類型擦除的局限性
- 運行時無法獲取泛型類型信息(如
new ArrayList<String>()運行時僅知道是ArrayList,不知道元素是String); - 無法實例化泛型類型的對象(如
new T()編譯報錯,因擦除后T是Object,無法確定具體類型); - 無法創(chuàng)建泛型類型的數(shù)組(如
new T[10]編譯報錯,因數(shù)組在運行時需知道具體類型)。
四、泛型的通配符:解決泛型類型的靈活性問題
泛型的 “類型參數(shù)” 是 “嚴(yán)格匹配” 的(如List<String>不是List<Object>的子類),但實際開發(fā)中常需 “靈活匹配多個類型”(如方法接收所有List子類)。泛型通配符(?)就是為解決此問題而生,分為無界通配符、上界通配符、下界通配符三類。
1. 無界通配符(?):匹配任意類型
無界通配符?表示 “任意類型”,常用于 “不關(guān)心泛型類型,僅使用原始類型方法” 的場景(如獲取集合大小、判斷是否為空)。
(1)語法格式
// 無界通配符:?表示任意類型 List\<?> list; // 可指向List\<String>、List\<Integer>、List\<User>等任意List
(2)代碼示例:無界通配符的使用
import java.util.ArrayList;
import java.util.List;
public class UnboundedWildcardDemo {
// 方法:打印任意List的大小和元素(不關(guān)心元素類型)
public static void printList(List\<?> list) {
System.out.println("List大?。? + list.size());
for (Object obj : list) { // 只能用Object接收元素(因類型未知)
System.out.print(obj + " ");
}
System.out.println();
}
public static void main(String\[] args) {
// 1. List\<String>
List\<String> strList = new ArrayList<>();
strList.add("A");
strList.add("B");
printList(strList); // 合法:List\<String>匹配List\<?>
// 2. List\<Integer>
List\<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
printList(intList); // 合法:List\<Integer>匹配List\<?>
// 3. 無界通配符的限制:無法添加非null元素(因類型未知,無法確定是否匹配)
List\<?> wildcardList = new ArrayList\<String>();
// wildcardList.add("C"); // 編譯報錯:無法確定類型,不允許添加
wildcardList.add(null); // 合法:null是任意類型的實例
}
}(3)核心特點:
- 優(yōu)點:靈活匹配任意泛型類型,適合 “只讀” 或 “不依賴元素類型” 的場景;
- 缺點:無法添加非
null元素(類型未知,避免添加不匹配類型),僅能通過Object接收元素。
2. 上界通配符(? extends T):匹配 T 及其子類
上界通配符? extends T表示 “任意繼承自 T 的類型”(T 是上界),常用于 “讀取元素” 的場景(如獲取集合中元素的最大值,元素需是 T 的子類)。
(1)語法格式
// 上界通配符:? extends T,匹配T及其子類 List\<? extends Number> list; // 可指向List\<Integer>、List\<Double>、List\<Number>(Integer和Double是Number的子類)
(2)代碼示例:上界通配符的使用
import java.util.ArrayList;
import java.util.List;
public class BoundedUpperWildcardDemo {
// 方法:計算List中所有數(shù)值的和(元素必須是Number的子類,如Integer、Double)
public static double sumList(List\<? extends Number> numberList) {
double sum = 0.0;
for (Number num : numberList) { // 可通過上界類型Number接收元素
sum += num.doubleValue(); // 調(diào)用Number的方法,安全
}
return sum;
}
public static void main(String\[] args) {
// 1. List\<Integer>(Integer extends Number)
List\<Integer> intList = new ArrayList<>();
intList.add(10);
intList.add(20);
System.out.println("Integer列表和:" + sumList(intList)); // 輸出:30.0
// 2. List\<Double>(Double extends Number)
List\<Double> doubleList = new ArrayList<>();
doubleList.add(15.5);
doubleList.add(25.5);
System.out.println("Double列表和:" + sumList(doubleList)); // 輸出:41.0
// 3. 上界通配符的限制:無法添加非null元素(除null外)
List\<? extends Number> upperList = new ArrayList\<Integer>();
// upperList.add(30); // 編譯報錯:無法確定具體是Number的哪個子類,避免添加不匹配類型
upperList.add(null); // 合法:null是任意類型的實例
}
}(3)核心特點:
- 優(yōu)點:可通過上界類型安全讀取元素(如
Number),適合 “只讀” 場景; - 缺點:無法添加非
null元素(類型不確定,如List<? extends Number>可能是List<Integer>,添加Double會出錯)。
3. 下界通配符(? super T):匹配 T 及其父類
下界通配符? super T表示 “任意 T 的父類型”(T 是下界),常用于 “寫入元素” 的場景(如向集合中添加 T 類型的元素,父類集合可接收子類元素)。
(1)語法格式
// 下界通配符:? super T,匹配T及其父類 List\<? super Integer> list; // 可指向List\<Integer>、List\<Number>、List\<Object>(Number和Object是Integer的父類)
(2)代碼示例:下界通配符的使用
import java.util.ArrayList;
import java.util.List;
public class BoundedLowerWildcardDemo {
// 方法:向List中添加多個Integer元素(List的類型必須是Integer或其父類)
public static void addIntegers(List\<? super Integer> list) {
list.add(1); // 合法:可添加Integer類型(下界類型)
list.add(2); // 合法:可添加Integer的子類(如Integer本身)
// list.add(3.5); // 編譯報錯:不能添加非Integer類型(如Double)
}
public static void main(String\[] args) {
// 1. List\<Integer>(Integer super Integer)
List\<Integer> intList = new ArrayList<>();
addIntegers(intList);
System.out.println("List\<Integer>添加后:" + intList); // 輸出:\[1, 2]
// 2. List\<Number>(Number super Integer)
List\<Number> numberList = new ArrayList<>();
numberList.add(100.5); // 先添加一個Double
addIntegers(numberList);
System.out.println("List\<Number>添加后:" + numberList); // 輸出:\[100.5, 1, 2]
// 3. List\<Object>(Object super Integer)
List\<Object> objectList = new ArrayList<>();
objectList.add("Hello"); // 先添加一個String
addIntegers(objectList);
System.out.println("List\<Object>添加后:" + objectList); // 輸出:\[Hello, 1, 2]
// 4. 下界通配符的限制:讀取元素只能用Object接收(因父類類型不確定)
for (Object obj : numberList) {
System.out.print(obj + " "); // 只能用Object接收
}
}
}(3)核心特點:
- 優(yōu)點:可安全添加下界類型(如
Integer)及其子類的元素,適合 “寫入” 場景; - 缺點:讀取元素只能用
Object接收(父類類型不確定,無法通過更具體的類型接收)。
4. 通配符使用口訣:PECS 原則
為簡化通配符的選擇,業(yè)界總結(jié)出PECS 原則(Producer Extends, Consumer Super):
- Producer(生產(chǎn)者):若泛型對象僅用于 “提供元素”(讀?。?code>? extends T(上界通配符);
- 例:
sumList(List<? extends Number> list)——list 是 “生產(chǎn)者”,提供 Number 類型元素用于求和。 - Consumer(消費者):若泛型對象僅用于 “接收元素”(寫入),用
? super T(下界通配符); - 例:
addIntegers(List<? super Integer> list)——list 是 “消費者”,接收 Integer 類型元素。 - 既是生產(chǎn)者又是消費者:不用通配符,直接用具體類型(如
List<T>)。
五、泛型的限制與注意事項
Java 泛型受限于類型擦除,存在一些無法突破的限制,開發(fā)中需避免這些場景。
1. 限制 1:不能實例化泛型類型的對象
因類型擦除后泛型類型變?yōu)?code>Object或上界類型,無法確定具體類型,故new T()編譯報錯:
class Box\<T> {
public Box() {
// T obj = new T(); // 編譯報錯:無法實例化泛型類型
// 解決方案:通過反射或傳入Class對象
// T obj = clazz.newInstance(); // 需傳入Class\<T> clazz參數(shù)
}
}2. 限制 2:不能用基本類型作為類型參數(shù)
泛型的類型參數(shù)必須是 “引用類型”(如Integer、String),不能是基本類型(如int、double),因類型擦除后會變?yōu)?code>Object,而基本類型無法賦值給Object:
// List\<int> list = new ArrayList<>(); // 編譯報錯:不能用基本類型int List\<Integer> list = new ArrayList<>(); // 合法:用包裝類Integer
3. 限制 3:不能創(chuàng)建泛型類型的數(shù)組
數(shù)組在運行時需知道具體類型,而泛型類型擦除后無法確定,故new T[10]編譯報錯:
class Box\<T> {
public void test() {
// T\[] arr = new T\[10]; // 編譯報錯:不能創(chuàng)建泛型數(shù)組
// 解決方案1:用Object數(shù)組,使用時強制轉(zhuǎn)換
Object\[] arr = new Object\[10];
T element = (T) arr\[0];
// 解決方案2:用ArrayList替代數(shù)組(推薦)
List\<T> list = new ArrayList<>();
}
}4. 限制 4:泛型類不能繼承 Throwable
泛型類無法繼承Exception、Error等 Throwable 子類,因異常處理(try-catch)在編譯時需確定類型,而泛型類型擦除后無法匹配:
// class GenericException\<T> extends Exception { } // 編譯報錯:泛型類不能繼承Throwable
class MyException extends Exception { } // 合法:非泛型類可繼承Exception5. 限制 5:靜態(tài)方法不能引用類的泛型參數(shù)
類的泛型參數(shù)屬于 “對象級”(創(chuàng)建對象時確定),而靜態(tài)方法屬于 “類級”(類加載時確定),二者生命周期不匹配,故靜態(tài)方法不能直接引用類的泛型參數(shù):
class Box\<T> {
// public static T getValue() { return null; } // 編譯報錯:靜態(tài)方法不能引用類的泛型參數(shù)
// 解決方案:靜態(tài)方法定義自己的泛型參數(shù)(泛型方法)
public static \<U> U getValue() { return null; } // 合法:靜態(tài)泛型方法
}六、泛型的實際應(yīng)用場景
泛型在 Java 開發(fā)中無處不在,核心應(yīng)用場景包括集合框架、工具類、框架設(shè)計等。
1. 場景 1:集合框架(最典型應(yīng)用)
Java 集合框架(List、Set、Map等)全部基于泛型實現(xiàn),確保元素類型安全:
// List\<String>:元素只能是String
List\<String> names = new ArrayList<>();
names.add("Alice");
String name = names.get(0); // 無需強轉(zhuǎn)
// Map\<Long, User>:鍵是Long,值是User
Map\<Long, User> userMap = new HashMap<>();
userMap.put(1001L, new User(1001L, "Bob"));
User user = userMap.get(1001L); // 無需強轉(zhuǎn)2. 場景 2:泛型工具類(代碼復(fù)用)
開發(fā)通用工具類(如排序、比較、轉(zhuǎn)換工具)時,用泛型實現(xiàn) “一套邏輯適配多種類型”:
// 泛型工具類:排序任意Comparable類型的數(shù)組
public class SortUtil {
public static \<T extends Comparable\<T>> void sort(T\[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr\[j].compareTo(arr\[j + 1]) > 0) {
T temp = arr\[j];
arr\[j] = arr\[j + 1];
arr\[j + 1] = temp;
}
}
}
}
}
// 使用:排序Integer數(shù)組和String數(shù)組
Integer\[] intArr = {3, 1, 2};
SortUtil.sort(intArr); // 輸出:\[1, 2, 3]
String\[] strArr = {"C", "A", "B"};
SortUtil.sort(strArr); // 輸出:\[A, B, C]3. 場景 3:框架設(shè)計(解耦與擴展)
主流框架(如 Spring、MyBatis)大量使用泛型實現(xiàn)靈活擴展,例如 MyBatis 的Mapper接口:
// 泛型接口:MyBatis Mapper,T是實體類,ID是主鍵類型
public interface BaseMapper\<T, ID> {
T selectById(ID id); // 根據(jù)主鍵查詢
int insert(T entity); // 插入實體
int update(T entity); // 更新實體
int deleteById(ID id); // 根據(jù)主鍵刪除
}
// 實現(xiàn)接口:UserMapper,T=User,ID=Long
public interface UserMapper extends BaseMapper\<User, Long> {
// 無需重復(fù)定義CRUD方法,直接繼承泛型接口
List\<User> selectByUsername(String username); // 新增自定義方法
}七、泛型的常見誤區(qū)與避坑指南
1. 誤區(qū) 1:混淆泛型類型與原始類型
- 問題:
List list = new ArrayList<String>();(原始類型 List 接收泛型對象),編譯時會有 “未檢查的轉(zhuǎn)換” 警告,且失去類型安全; - 解決:始終用泛型類型接收泛型對象(如
List<String> list = new ArrayList<>();),避免使用原始類型。
2. 誤區(qū) 2:錯誤使用通配符導(dǎo)致添加元素失敗
- 問題:
List<? extends Number> list = new ArrayList<Integer>(); list.add(1);(編譯報錯),誤以為上界通配符可添加元素; - 原因:上界通配符
? extends Number可能指向List<Double>,添加Integer會類型不匹配; - 解決:添加元素用下界通配符(
? super T),如List<? super Integer> list = new ArrayList<>(); list.add(1);。
3. 誤區(qū) 3:泛型方法的類型參數(shù)與類的類型參數(shù)重名
- 問題:
class Box\<T> {
// 泛型方法的類型參數(shù)T與類的T重名,導(dǎo)致混淆
public \<T> T getValue(T param) { return param; }
}- 影響:方法的
T會隱藏類的T,導(dǎo)致類的T無法在方法中使用; - 解決:泛型方法的類型參數(shù)用不同名稱(如
U),避免重名,如public <U> U getValue(U param) { return param; }。
4. 誤區(qū) 4:誤以為泛型可實現(xiàn) “運行時類型判斷”
- 問題:
if (list instanceof List<String>) { ... }(編譯報錯),試圖在運行時判斷泛型類型; - 原因:類型擦除后,運行時
List<String>和List<Integer>都是List,無法區(qū)分; - 解決:若需判斷元素類型,遍歷集合檢查每個元素的類型(如
if (list.get(0) instanceof String))。
八、總結(jié):泛型的核心要點與實踐建議
1. 核心要點
- 本質(zhì):參數(shù)化類型,編譯時類型安全,運行時類型擦除;
- 語法:泛型類(
class A<T>)、泛型接口(interface B<T>)、泛型方法(<T> T method(T param)); - 通配符:PECS 原則(生產(chǎn)者
extends,消費者super),無界通配符用于只讀且不關(guān)心類型; - 限制:不能實例化泛型對象、不能用基本類型、不能創(chuàng)建泛型數(shù)組、泛型類不能繼承 Throwable。
2. 實踐建議
- 優(yōu)先使用泛型:定義集合、工具類時強制使用泛型,避免原始類型,確保類型安全;
- 合理選擇通配符:讀取用
extends,寫入用super,既讀又寫用具體類型; - 泛型方法優(yōu)先于泛型類:若僅方法需要通用邏輯,定義泛型方法(無需創(chuàng)建泛型類),提高代碼復(fù)用;
- 避免過度泛型化:僅在需要適配多種類型時使用泛型,簡單場景直接用具體類型,避免代碼復(fù)雜度。
泛型是 Java 類型系統(tǒng)的重要擴展,掌握泛型的語法與原理,是編寫類型安全、高復(fù)用代碼的基礎(chǔ),也是后續(xù)學(xué)習(xí)集合框架、框架源碼(如 Spring、MyBatis)的關(guān)鍵前提。
到此這篇關(guān)于Java SE 泛型原理、語法與實踐詳解?的文章就介紹到這了,更多相關(guān)java se泛型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深度解析Java中的國際化底層類ResourceBundle
做項目應(yīng)該都會實現(xiàn)國際化,那么大家知道Java底層是如何實現(xiàn)國際化的嗎?這篇文章就來和大家深度解析一下Java中的國際化底層類ResourceBundle,希望對大家有所幫助2023-03-03
Spring?Cloud詳細(xì)講解zuul集成Eureka流程
這篇文章主要介紹了Spring?Cloud?zuul集成Eureka,Eureka?Client中內(nèi)置一個負(fù)載均衡器,用來進(jìn)行基本的負(fù)載均衡,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06
基于Java實現(xiàn)一個簡單的單詞本Android App的實踐
本文基于Java實現(xiàn)了一個簡單的單詞本安卓app,用的是SQLite數(shù)據(jù)庫,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01
java 中 String format 和Math類實例詳解
這篇文章主要介紹了java 中 String format 和Math類實例詳解的相關(guān)資料,需要的朋友可以參考下2017-06-06
Java常用鎖synchronized和ReentrantLock的區(qū)別
這篇文章主要介紹了Java常用鎖synchronized和ReentrantLock的區(qū)別,二者的功效都是相同的,但又有很多不同點,下面我們就進(jìn)入文章了解具體的相關(guān)內(nèi)容吧。需要的小伙伴也可以參考一下2022-05-05

