目錄
一、概述
二、為什么使用泛型
三、常見泛型類型參數的含義與用途
示例一:使用 T 定義泛型類
示例二:使用 E 表示集合元素
示例三:使用 K 和 V 表示鍵值對
示例四:使用 ? 通配符處理未知類型
四、通配符 ? 的擴展用法
無界通配符:?
上限通配符:? extends T
下限通配符:? super T
五、泛型類別
泛型類
泛型接口
泛型方法
六、泛型的類型表示:Type 接口的實現
ParameterizedType:參數化類型
TypeVariable:類型變量
WildcardType:通配符類型
七、其他泛型寫法
多邊界類型參數
通配符嵌套
遞歸類型約束
泛型與函數式接口結合
泛型數組的創建技巧
八、注意事項
總結
Java 泛型(Generics)是 Java 5 引入的重要特性之一,它允許在類、接口和方法中使用類型參數,從而實現類型安全的復用代碼。泛型不僅提高了代碼的可重用性,還增強了編譯時的類型檢查,避免了運行時的類型轉換錯誤。
元素如何保存,如何管理等是確定的,因此此時把元素的類型設計成一個參數,這個類型參數叫做泛型。如:Collection<E>,List<E>,ArrayList<E>這個<E>就是類型參數,即泛型
在使用泛型的過程中,我們經常會看到 T、E、K、V、? 等符號,它們分別代表不同的類型參數。本文將詳細解析這些符號的含義、用途以及最佳實踐,幫助你不再“傻傻分不清”。
一、概述
泛型的核心思想是 “參數化類型”,即把類型作為參數傳遞給類或方法。例如:
List<String> list =?new?ArrayList<>();
這里的 String 就是類型參數,表示這個列表只能存放字符串類型的元素。
二、為什么使用泛型
泛型的好處是在編譯的時候檢查類型安全,并且所有的強制轉換都是自動和隱式的,提高代碼的重用率。
-
保證了類型的安全性:在沒有泛型之前,從集合中讀取到的每一個對象都必須進行類型轉換,如果不小心插入了錯誤的類型對象,在運行時的轉換處理就會出錯
-
消除強制轉換:泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換,這使得代碼更加可讀,并且減少了出錯機會
-
避免了不必要的裝箱、拆箱操作,提高程序的性能
-
提高了代碼的重用性
三、常見泛型類型參數的含義與用途
Java 泛型中并沒有強制規定必須使用哪些字母作為類型參數,但為了代碼的可讀性和一致性,社區中形成了一些約定俗成的命名習慣。
類型參數 | 含義 | 常見使用場景 |
---|---|---|
T | Type(類型) | 表示任意具體類型,常用于類、接口、方法級別的泛型定義 |
E | Element(元素) | 多用于集合類中,表示集合中的元素類型,如? |
K | Key(鍵) | 通常用于 Map 中的鍵類型,如? |
V | Value(值) | 通常用于 Map 中的值類型,如? |
N | Number(數字) | 表示數值類型,如? |
? | Unknown(未知) | 表示不確定的類型,用于通配符(Wildcard) |
S, U, V | 第二、第三、第四個類型參數 | 當一個泛型需要多個類型參數時使用 |
示例一:使用 T 定義泛型類
public?class?Box<T>?{private?T item;public?void?setItem(T item)?{this.item = item;}public?T?getItem()?{return?item;}
}
調用方式:
Box<String> stringBox =?new?Box<>();
stringBox.setItem("Hello");Box<Integer> integerBox =?new?Box<>();
integerBox.setItem(123);
示例二:使用 E 表示集合元素
public?interface?List<E>?extends?Collection<E>?{// ...
}
示例三:使用 K 和 V 表示鍵值對
public?interface?Map<K,?V>?{V?put(K key, V value);V?get(Object key);
}
示例四:使用 ? 通配符處理未知類型
public?static?void?printList(List<?> list)?{for?(Object obj : list) {System.out.println(obj);}
}
此方法可以接受任何類型的 List,比如 List、List等。
四、通配符 ? 的擴展用法
通配符 ? 可以配合上下限使用,實現更靈活的類型約束。
無界通配符:?
適用于不知道或不關心具體類型的情況。
List<?> anyList =?new?ArrayList<String>();
上限通配符:? extends T
表示某種類型及其子類,適合“只讀”操作。
public?static?void?addNumbers(List<? extends Number> list)?{for?(Number number : list) {System.out.println(number.doubleValue());}
}
“注意:不能向這種列表中添加元素(除了 null),因為編譯器無法確定具體的類型。
下限通配符:? super T
表示某種類型及其父類,適合“寫入”操作。
public?static?void?addIntegers(List<??super?Integer> list)?{list.add(100);?// 正確:Integer 是下限的子類
}
此時你可以往列表中添加 Integer 類型的數據,但不能保證讀取的是什么類型。
五、泛型類別
泛型根據定義位置和作用范圍,可分為泛型類、泛型接口和泛型方法三類。它們的核心都是 “參數化類型”,但適用場景和語法略有差異。
泛型類
泛型類是指在類定義時聲明類型參數,使得類中的字段、方法參數或返回值可以使用這些類型參數。類型參數的作用域貫穿整個類。
public?class?DataWrapper<T>?{private?T data;public?DataWrapper(T data)?{this.data = data;}public?T?getData()?{return?data;}public?void?setData(T data)?{this.data = data;}
}
? ? ? ? ?泛型類繼承,若父類指定類型,則子類不用指定,默認和父類一樣。若父類沒有指定泛型類型,則父類、子類都需要默認泛型(Object)
泛型接口
泛型接口與泛型類類似,在接口定義時聲明類型參數,實現類需指定具體類型或繼續保留泛型。
// 定義泛型接口,E表示產出的元素類型
public?interface?Producer<E>?{E?produce();
}// 實現接口時指定具體類型(如String)
public?class?StringProducer?implements?Producer<String>?{@Overridepublic?String?produce()?{return?"Hello";}
}// 實現接口時繼續保留泛型(泛型實現類)
public?class?GenericProducer<T>?implements?Producer<T>?{private?T data;public?GenericProducer(T data)?{this.data = data;}@Overridepublic?T?produce()?{return?data;}
}
泛型方法
泛型方法是指在方法聲明時單獨聲明類型參數的方法,其類型參數的作用域僅限于當前方法。它可以是靜態方法或實例方法,且無需依賴所在類是否為泛型類。
public?class?GenericMethodDemo?{// 泛型實例方法:將輸入對象轉換為目標類型(簡化示例)public?<T>?T?convert(Object obj, Class<T> clazz)?throws?Exception?{if?(clazz.isInstance(obj)) {return?clazz.cast(obj);}// 實際場景可能通過反射、JSON等方式轉換return?clazz.getConstructor(String.class).newInstance(obj.toString());}// 靜態泛型方法:創建指定類型的列表并添加元素public?static?<E>?List<E>?createList(E... elements)?{List<E> list =?new?ArrayList<>();for?(E e : elements) {list.add(e);}return?list;}
}// 使用泛型方法
public?static?void?main(String[] args)?throws?Exception?{GenericMethodDemo demo =?new?GenericMethodDemo();// 實例泛型方法:將String轉換為IntegerInteger num = demo.convert("123", Integer.class);// 靜態泛型方法:創建String列表List<String> strList = GenericMethodDemo.createList("a",?"b",?"c");
}
關鍵特性
-
泛型方法的類型參數聲明(如)必須在返回值前,這是區分泛型方法與普通方法的核心標志;
-
靜態方法無法使用所在類的泛型參數(因靜態成員屬于類,與實例無關),若需泛型需定義為靜態泛型方法;
六、泛型的類型表示:Type 接口的實現
在 Java 中,泛型的類型信息在編譯后會被部分保留(類型擦除不會完全抹去所有信息),這些信息可通過反射 API 獲取。java.lang.reflect.Type接口是所有泛型類型的父接口,其下有 5 種核心實現(或子接口),用于表示不同場景的泛型類型。
Class<?>:非泛型類型
Class是Type的直接實現,用于表示非泛型類型(如String、Integer)或泛型類型的原始類型(如List,未指定類型參數的泛型類)。
// String是普通類,類型為Class
Class<String> stringClass = String.class;
// List是泛型類的原始類型,類型仍為Class
Class<List> listClass = List.class;
ParameterizedType:參數化類型
表示指定了具體類型參數的泛型類型,如List、Map<Integer, String>。其核心方法包括:
-
getRawType():獲取原始類型(如List的原始類型是List.class);
-
getActualTypeArguments():獲取實際類型參數(如List的實際參數是String.class)。
public?class?ParameterizedTypeDemo?{private?List<String> stringList;private?Map<Integer, List<String>> complexMap;public?static?void?main(String[] args)?throws?NoSuchFieldException?{// 獲取stringList字段的類型Field field1 = ParameterizedTypeDemo.class.getDeclaredField("stringList");ParameterizedType pt1 = (ParameterizedType) field1.getGenericType();System.out.println(pt1.getRawType());?// interface java.util.ListSystem.out.println(pt1.getActualTypeArguments()[0]);?// class java.lang.String// 獲取complexMap字段的類型Field field2 = ParameterizedTypeDemo.class.getDeclaredField("complexMap");ParameterizedType pt2 = (ParameterizedType) field2.getGenericType();System.out.println(pt2.getRawType());?// interface java.util.MapSystem.out.println(pt2.getActualTypeArguments()[0]);?// class java.lang.IntegerSystem.out.println(pt2.getActualTypeArguments()[1]);?// java.util.List<java.lang.String>(也是ParameterizedType)}
}
TypeVariable:類型變量
表示泛型中的類型參數(如類或方法聲明的T、E)。其核心方法包括:
-
getName():獲取類型參數名稱(如T);
-
getBounds():獲取類型參數的上限(如class Box中,T的上限是Number.class)。
public?class?TypeVariableDemo<T?extends?Number?&?Serializable,?E>?{public?static?void?main(String[] args)?{// 獲取類的泛型類型參數TypeVariable<Class<TypeVariableDemo>>[] typeVariables = TypeVariableDemo.class.getTypeParameters();// 第一個類型參數TTypeVariable<Class<TypeVariableDemo>> t = typeVariables[0];System.out.println(t.getName());?// TSystem.out.println(Arrays.toString(t.getBounds()));?// [class java.lang.Number, interface java.io.Serializable]// 第二個類型參數E(無顯式上限,默認上限為Object)TypeVariable<Class<TypeVariableDemo>> e = typeVariables[1];System.out.println(e.getName());?// ESystem.out.println(Arrays.toString(e.getBounds()));?// [class java.lang.Object]}
}
WildcardType:通配符類型
表示泛型通配符(如?、? extends Number、? super Integer)。其核心方法包括:
-
getUpperBounds():獲取上限(如? extends Number的上限是Number.class);
-
getLowerBounds():獲取下限(如? super Integer的下限是Integer.class)。
public?class?WildcardTypeDemo?{private?List<? extends Number> upperList;private?List<??super?Integer> lowerList;public?static?void?main(String[] args)?throws?NoSuchFieldException?{// 獲取上限通配符Field field1 = WildcardTypeDemo.class.getDeclaredField("upperList");ParameterizedType pt1 = (ParameterizedType) field1.getGenericType();WildcardType wt1 = (WildcardType) pt1.getActualTypeArguments()[0];System.out.println(Arrays.toString(wt1.getUpperBounds()));?// [class java.lang.Number]System.out.println(Arrays.toString(wt1.getLowerBounds()));?// [](無下限)// 獲取下限通配符Field field2 = WildcardTypeDemo.class.getDeclaredField("lowerList");ParameterizedType pt2 = (ParameterizedType) field2.getGenericType();WildcardType wt2 = (WildcardType) pt2.getActualTypeArguments()[0];System.out.println(Arrays.toString(wt2.getUpperBounds()));?// [class java.lang.Object](默認上限)System.out.println(Arrays.toString(wt2.getLowerBounds()));?// [class java.lang.Integer]}
}
為什么需要了解 Type 接口?
在日常開發中,直接使用這些類型的場景較少,但在框架開發(如 Spring、Jackson)中,反射處理泛型是常見需求。例如:
-
Jackson 解析List時,需通過ParameterizedType獲取User類型;
-
Spring 的ResolvableType工具類封裝了對這些類型的處理,用于依賴注入時的泛型匹配。
七、其他泛型寫法
Java 泛型的高級寫法主要體現在多邊界約束、通配符嵌套、泛型方法重載和遞歸類型約束等場景。以下是一些實用的高級寫法及示例:
多邊界類型參數
當需要對泛型類型參數施加多個約束條件時,可以使用 & 符號連接多個接口(或一個類和多個接口)。注意:類必須放在第一個位置,且只能有一個類。
// 要求T必須同時實現Serializable和Cloneable接口
public?static?class?Custom<T?extends?Serializable?&?Cloneable>?{private?T value;public?Custom(T value)?{this.value = value;}
}public?static?class?Student?implements?Serializable,?Cloneable?{}public?static?void?main(String[] args)?{Custom<Student> custom =?new?Custom<>(new?Student());
}
通配符嵌套
在處理復雜泛型結構時,通配符可以嵌套使用,實現更精確的類型約束。
// 嵌套通配符示例:表示一個列表,其元素是另一個列表,內層列表的元素類型是Number或其子類
List<? extends List<? extends Number>> nestedList;// 使用場景:統計嵌套列表中的所有數值之和
public?static?double?sumNestedLists(List<? extends List<? extends Number>> lists)?{double?sum =?0;for?(List<? extends Number> innerList : lists) {for?(Number num : innerList) {sum += num.doubleValue();}}return?sum;
}
遞歸類型約束
允許類型參數引用自身,常用于定義 “可比較自身的類型” 或 “自引用接口”。
// 定義一個可排序的元素,要求T必須實現Comparable接口且能比較自身類型
public?static?class?SortedElement<T?extends?Comparable<T>>?{private?T value;public?SortedElement(T value)?{this.value = value;}// 判斷當前元素是否小于另一個元素public?boolean?isLessThan(SortedElement<T> other)?{return?this.value.compareTo(other.value) <?0;}
}public?static?void?main(String[] args)?{SortedElement<Integer> e1 =?new?SortedElement<>(10);SortedElement<Integer> e2 =?new?SortedElement<>(20);System.out.println(e1.isLessThan(e2));?// true
}
泛型與函數式接口結合
利用 Java 8+ 的函數式接口和泛型,實現更靈活的類型安全操作。
// 定義一個泛型函數式接口,用于轉換類型@FunctionalInterfacepublic?interface?Converter<T,?R>?{R?convert(T source);}// 泛型工具類:提供類型轉換方法public?static?class?GenericConverter?{// 使用泛型和函數式接口進行類型轉換public?static?<T, R>?List<R>?convertList(List<T> sourceList, Converter<T, R> converter)?{List<R> result =?new?ArrayList<>();for?(T item : sourceList) {result.add(converter.convert(item));}return?result;}}public?static?void?main(String[] args)?{List<String> stringList = Arrays.asList("1",?"2",?"3");List<Integer> integerList = GenericConverter.convertList(stringList, Integer::valueOf);}
泛型數組的創建技巧
由于類型擦除,直接創建泛型數組(如 new T[])是非法的,但可以通過反射創建。
public?static?class?GenericArray<T>?{private?T[] array;@SuppressWarnings("unchecked")public?GenericArray(Class<T> clazz,?int?size)?{// 通過Array.newInstance創建泛型數組this.array = (T[]) Array.newInstance(clazz, size);}public?T[] getArray() {return?array;}public?void?set(int?index, T value)?{array[index] = value;}
}public?static?void?main(String[] args)?{GenericArray<String> stringArray =?new?GenericArray<>(String.class, 3);stringArray.set(0,?"Hello");stringArray.set(1,?"world");
}
八、注意事項
-
不能使用基本類型作為泛型參數,如 List是非法的,應使用包裝類 List。
-
不能創建泛型數組,如 new T[10] 是非法的,因為類型擦除后無法知道實際類型。
-
Java 的泛型是通過類型擦除實現的,也就是說,在運行時,泛型信息會被擦除,所有泛型類型最終都變成其上限(通常是 Object)。
-
不能實例化泛型類型對象,如 new T() 是非法的,因為運行時不知道具體類型。
-
泛型類型參數無法用于 instanceof 檢查
-
泛型類的靜態成員無法使用類的類型參數
總結
Java 泛型通過 “參數化類型” 實現了類型安全與代碼復用,T、E、K、V 等符號是約定俗成的類型參數命名,而?通配符則靈活處理了未知類型的場景。
深入理解泛型類、接口、方法的定義與使用,以及 Type 接口的實現類(如 ParameterizedType、TypeVariable),不僅能寫出更健壯的代碼,還能為理解框架底層的泛型處理(如反射、類型解析)打下基礎。
?
今日語錄:沖鋒吧,爺們!
?