JAVA 泛型與通配符詳解從原理到實戰(zhàn)應(yīng)用
1.1 本章學(xué)習(xí)目標與重點
?? 掌握泛型的核心概念與設(shè)計初衷,理解泛型的編譯期檢查機制。
?? 熟練使用泛型類、泛型接口和泛型方法,解決數(shù)據(jù)類型安全問題。
?? 理解通配符(?)、上界通配符(? extends T)和下界通配符(? super T)的使用場景。
?? 本章重點是 泛型的擦除機制 和 通配符的靈活運用,這是提升代碼通用性和安全性的關(guān)鍵。
1.2 泛型的核心概念與設(shè)計初衷
1.2.1 為什么需要泛型
在沒有泛型的 JDK 5 之前,集合類只能存儲 Object 類型的對象。獲取元素時需要強制類型轉(zhuǎn)換,這會帶來兩個嚴重問題:
- 類型不安全:可以向集合中添加任意類型的對象,運行時可能拋出
ClassCastException。 - 代碼臃腫:頻繁的強制類型轉(zhuǎn)換會讓代碼可讀性和維護性變差。
?? 泛型的出現(xiàn)就是為了解決這些問題,它的核心思想是 將類型參數(shù)化。在定義類、接口或方法時,用一個標識符表示類型,使用時再指定具體類型。
泛型可以在編譯期進行類型檢查,避免運行時的類型轉(zhuǎn)換異常,同時簡化代碼。
1.2.2 泛型的核心優(yōu)勢
- 編譯期類型安全:編譯器會檢查集合中元素的類型,不允許添加錯誤類型的對象。
- 消除強制類型轉(zhuǎn)換:獲取元素時無需手動轉(zhuǎn)換類型,代碼更簡潔。
- 代碼復(fù)用性高:一套泛型代碼可以適配多種數(shù)據(jù)類型,無需重復(fù)編寫。
? 核心結(jié)論:泛型是一種編譯期技術(shù),它的作用是在編譯階段保證類型安全,運行時泛型信息會被擦除。
1.3 泛型的三種使用方式
1.3.1 泛型類
?? 泛型類是在類的定義時聲明類型參數(shù),語法格式為 class 類名<T>。其中 T 是類型參數(shù),可以是任意標識符,常用的有 T(Type)、E(Element)、K(Key)、V(Value)。
代碼實操:自定義泛型集合類
我們實現(xiàn)一個簡單的泛型順序表 GenericArrayList,支持任意類型的數(shù)據(jù)存儲:
public class GenericArrayList<T> {
// 底層數(shù)組,存儲泛型類型數(shù)據(jù)
private Object[] elementData;
// 集合大小
private int size;
// 默認初始容量
private static final int DEFAULT_CAPACITY = 10;
// 無參構(gòu)造
public GenericArrayList() {
elementData = new Object[DEFAULT_CAPACITY];
}
// 有參構(gòu)造,指定初始容量
public GenericArrayList(int initialCapacity) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("初始容量不能為負數(shù):" + initialCapacity);
}
elementData = new Object[initialCapacity];
}
// 添加元素
public void add(T t) {
// 擴容判斷
if (size == elementData.length) {
grow();
}
elementData[size++] = t;
}
// 獲取元素
@SuppressWarnings("unchecked")
public T get(int index) {
// 索引合法性檢查
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界:" + index);
}
// 強制類型轉(zhuǎn)換,通過泛型保證類型安全
return (T) elementData[index];
}
// 數(shù)組擴容
private void grow() {
int oldCapacity = elementData.length;
// 擴容為原來的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
elementData = java.util.Arrays.copyOf(elementData, newCapacity);
}
// 獲取集合大小
public int size() {
return size;
}
// 測試方法
public static void main(String[] args) {
// 1. 存儲String類型
GenericArrayList<String> strList = new GenericArrayList<>();
strList.add("Java");
strList.add("泛型");
// 編譯期檢查,不允許添加非String類型
// strList.add(123); 編譯報錯
String str = strList.get(0);
System.out.println("String集合元素:" + str);
// 2. 存儲Integer類型
GenericArrayList<Integer> intList = new GenericArrayList<>();
intList.add(1);
intList.add(2);
Integer num = intList.get(1);
System.out.println("Integer集合元素:" + num);
}
}輸出結(jié)果
String集合元素:Java
Integer集合元素:2
?? 注意事項:
- 泛型類的類型參數(shù)不能是基本數(shù)據(jù)類型,只能是引用數(shù)據(jù)類型。如果需要存儲基本類型,需使用對應(yīng)的包裝類,如
Integer代替int。 - 泛型類的靜態(tài)方法中不能使用類的泛型參數(shù),因為靜態(tài)方法屬于類,而泛型參數(shù)是在創(chuàng)建對象時指定的。
1.3.2 泛型接口
?? 泛型接口的定義與泛型類類似,語法格式為 interface 接口名<T>。實現(xiàn)泛型接口時,可以指定具體類型,也可以繼續(xù)使用泛型參數(shù)。
代碼實操:自定義泛型迭代器接口
// 泛型迭代器接口
public interface GenericIterator<T> {
// 判斷是否有下一個元素
boolean hasNext();
// 獲取下一個元素
T next();
}
// 實現(xiàn)類1:指定具體類型為String
public class StringIterator implements GenericIterator<String> {
private String[] array;
private int index;
public StringIterator(String[] array) {
this.array = array;
this.index = 0;
}
@Override
public boolean hasNext() {
return index < array.length;
}
@Override
public String next() {
return array[index++];
}
}
// 實現(xiàn)類2:繼續(xù)使用泛型參數(shù)
public class ArrayIterator<T> implements GenericIterator<T> {
private T[] array;
private int index;
public ArrayIterator(T[] array) {
this.array = array;
this.index = 0;
}
@Override
public boolean hasNext() {
return index < array.length;
}
@Override
public T next() {
return array[index++];
}
}
// 測試類
public class GenericIteratorTest {
public static void main(String[] args) {
// 測試StringIterator
String[] strArray = {"A", "B", "C"};
GenericIterator<String> strIterator = new StringIterator(strArray);
while (strIterator.hasNext()) {
System.out.println(strIterator.next());
}
// 測試ArrayIterator
Integer[] intArray = {1, 2, 3};
GenericIterator<Integer> intIterator = new ArrayIterator<>(intArray);
while (intIterator.hasNext()) {
System.out.println(intIterator.next());
}
}
}輸出結(jié)果
A
B
C
1
2
3
1.3.3 泛型方法
?? 泛型方法是在方法聲明時指定類型參數(shù),它可以定義在普通類中,也可以定義在泛型類中。語法格式為 public <T> 返回值類型 方法名(T 參數(shù))。
泛型方法的核心特點是 類型參數(shù)由方法的調(diào)用者決定,與類的泛型參數(shù)無關(guān)。
代碼實操:泛型工具方法封裝
import java.util.Arrays;
public class GenericMethodUtil {
// 泛型方法:數(shù)組轉(zhuǎn)集合
public static <T> GenericArrayList<T> arrayToList(T[] array) {
GenericArrayList<T> list = new GenericArrayList<>();
for (T t : array) {
list.add(t);
}
return list;
}
// 泛型方法:交換數(shù)組中兩個元素的位置
public static <T> void swap(T[] array, int i, int j) {
if (i < 0 || i >= array.length || j < 0 || j >= array.length) {
throw new IndexOutOfBoundsException("索引越界");
}
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
// 測試方法
public static void main(String[] args) {
// 測試數(shù)組轉(zhuǎn)集合
String[] strArray = {"Java", "泛型", "方法"};
GenericArrayList<String> strList = GenericMethodUtil.arrayToList(strArray);
System.out.println("數(shù)組轉(zhuǎn)集合大?。? + strList.size());
// 測試交換數(shù)組元素
Integer[] intArray = {10, 20, 30, 40};
System.out.println("交換前數(shù)組:" + Arrays.toString(intArray));
GenericMethodUtil.swap(intArray, 1, 3);
System.out.println("交換后數(shù)組:" + Arrays.toString(intArray));
}
}輸出結(jié)果
數(shù)組轉(zhuǎn)集合大小:3
交換前數(shù)組:[10, 20, 30, 40]
交換后數(shù)組:[10, 40, 30, 20]
?? 技巧:泛型方法可以配合可變參數(shù)使用,進一步提升靈活性。例如:
public static <T> GenericArrayList<T> createList(T... elements) {
GenericArrayList<T> list = new GenericArrayList<>();
for (T t : elements) {
list.add(t);
}
return list;
}
// 調(diào)用方式
GenericArrayList<Integer> list = GenericMethodUtil.createList(1, 2, 3, 4);1.4 泛型的高級特性
1.4.1 泛型的擦除機制
?? 泛型是編譯期特性,在運行時 JVM 會擦除泛型信息,這個過程稱為類型擦除。
類型擦除的規(guī)則:
- 泛型類、接口和方法的類型參數(shù)會被擦除為其上限類型,如果沒有指定上限,則擦除為
Object。 - 編譯器會在必要的地方插入強制類型轉(zhuǎn)換代碼,保證運行時的類型正確性。
例如,我們定義的 GenericArrayList<String> 在運行時會被擦除為 GenericArrayList<Object>。獲取元素時,編譯器會自動插入 (String) 強制類型轉(zhuǎn)換代碼。
類型擦除的驗證
通過反射可以驗證類型擦除的存在,因為反射是運行時機制,可以獲取到擦除后的類型信息:
import java.lang.reflect.Field;
public class GenericErasureTest {
public static void main(String[] args) throws NoSuchFieldException {
GenericArrayList<String> strList = new GenericArrayList<>();
// 通過反射獲取底層數(shù)組的類型
Field field = GenericArrayList.class.getDeclaredField("elementData");
field.setAccessible(true);
Class<?> type = field.getType().getComponentType();
System.out.println("底層數(shù)組的類型:" + type.getName());
}
}輸出結(jié)果
底層數(shù)組的類型:java.lang.Object
?? 注意事項:類型擦除會導(dǎo)致泛型不支持重載。例如,以下兩個方法在編譯后會變成相同的簽名,編譯器會報錯:
// 編譯報錯:方法重復(fù)定義
public void method(GenericArrayList<String> list) {}
public void method(GenericArrayList<Integer> list) {}1.4.2 泛型的上下界限定
?? 在定義泛型時,可以通過 extends 關(guān)鍵字限制類型參數(shù)的上限,通過 super 關(guān)鍵字限制類型參數(shù)的下限(僅在通配符中使用)。
上限限定的語法:<T extends 類型>,表示 T 必須是該類型的子類或本身。
多個上限的語法:<T extends 類型1 & 類型2>,表示 T 必須同時實現(xiàn)多個接口(類只能有一個,且放在最前面)。
代碼實操:泛型上限限定
我們實現(xiàn)一個泛型方法,用于計算數(shù)組中元素的總和,要求元素類型必須是 Number 的子類:
public class GenericBoundsTest {
// 泛型上限限定:T必須是Number的子類
public static <T extends Number> double sum(T[] array) {
double sum = 0.0;
for (T t : array) {
sum += t.doubleValue();
}
return sum;
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
System.out.println("Integer數(shù)組總和:" + sum(intArray));
Double[] doubleArray = {1.1, 2.2, 3.3};
System.out.println("Double數(shù)組總和:" + sum(doubleArray));
// String類型不是Number的子類,編譯報錯
// String[] strArray = {"1", "2"};
// sum(strArray);
}
}輸出結(jié)果
Integer數(shù)組總和:15.0
Double數(shù)組總和:6.6
1.5 通配符的使用與場景
1.5.1 通配符(?)的基本使用
?? 通配符 ? 表示任意類型,它可以用來解決泛型的類型不兼容問題。
例如,GenericArrayList<Object> 不能接收 GenericArrayList<String> 對象,因為泛型是不協(xié)變的。此時可以使用通配符 GenericArrayList<?> 來接收任意類型的泛型實例。
代碼實操:通配符的基本應(yīng)用
public class WildcardBasicTest {
// 使用通配符接收任意類型的GenericArrayList
public static void printList(GenericArrayList<?> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println("元素:" + list.get(i));
}
}
public static void main(String[] args) {
GenericArrayList<String> strList = new GenericArrayList<>();
strList.add("Hello");
strList.add("Wildcard");
GenericArrayList<Integer> intList = new GenericArrayList<>();
intList.add(100);
intList.add(200);
// 可以接收任意類型的泛型集合
printList(strList);
printList(intList);
}
}輸出結(jié)果
元素:Hello
元素:Wildcard
元素:100
元素:200
?? 注意事項:使用無界通配符 <?> 的集合,只能讀取元素,不能添加元素(除了 null)。因為編譯器無法確定集合的具體類型,添加任何類型的元素都可能破壞類型安全。
1.5.2 上界通配符(? extends T)
?? 上界通配符 ? extends T 表示任意 T 的子類或 T 本身。它的核心作用是 限定讀取的上限,適合只讀的場景。
代碼實操:上界通配符的應(yīng)用
我們實現(xiàn)一個方法,用于獲取泛型集合中的最大值,要求元素類型必須實現(xiàn) Comparable 接口:
public class WildcardExtendsTest {
// 上界通配符:T必須實現(xiàn)Comparable接口
public static <T extends Comparable<T>> T getMax(GenericArrayList<? extends T> list) {
if (list.size() == 0) {
return null;
}
T max = list.get(0);
for (int i = 1; i < list.size(); i++) {
T current = list.get(i);
if (current.compareTo(max) > 0) {
max = current;
}
}
return max;
}
public static void main(String[] args) {
GenericArrayList<Integer> intList = new GenericArrayList<>();
intList.add(5);
intList.add(12);
intList.add(3);
System.out.println("Integer集合最大值:" + getMax(intList));
GenericArrayList<String> strList = new GenericArrayList<>();
strList.add("Apple");
strList.add("Banana");
strList.add("Orange");
System.out.println("String集合最大值:" + getMax(strList));
}
}輸出結(jié)果
Integer集合最大值:12
String集合最大值:Orange
? 核心結(jié)論:上界通配符 ? extends T 適合讀取數(shù)據(jù)的場景,它可以接收 T 及其子類的泛型實例。
1.5.3 下界通配符(? super T)
?? 下界通配符 ? super T 表示任意 T 的父類或 T 本身。它的核心作用是 限定寫入的下限,適合只寫的場景。
代碼實操:下界通配符的應(yīng)用
我們實現(xiàn)一個方法,用于向泛型集合中添加指定類型的元素:
public class WildcardSuperTest {
// 下界通配符:向集合中添加Integer類型的元素
public static void addIntegers(GenericArrayList<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
// 可以添加Integer及其子類的元素
// list.add(new Number()); 編譯報錯,Number是Integer的父類
}
public static void main(String[] args) {
// 情況1:集合類型為Integer
GenericArrayList<Integer> intList = new GenericArrayList<>();
addIntegers(intList);
System.out.println("Integer集合大小:" + intList.size());
// 情況2:集合類型為Number(Integer的父類)
GenericArrayList<Number> numList = new GenericArrayList<>();
addIntegers(numList);
System.out.println("Number集合大?。? + numList.size());
}
}輸出結(jié)果
Integer集合大?。?
Number集合大小:3
? 核心結(jié)論:下界通配符 ? super T 適合寫入數(shù)據(jù)的場景,它可以接收 T 及其父類的泛型實例。
1.5.4 通配符的使用原則:PECS 原則
?? PECS 原則是通配符使用的黃金法則,全稱是 Producer Extends, Consumer Super。
- Producer Extends:如果泛型對象是生產(chǎn)者(提供數(shù)據(jù),只讀),使用
? extends T。 - Consumer Super:如果泛型對象是消費者(消費數(shù)據(jù),只寫),使用
? super T。
例如:
- 讀取數(shù)據(jù)的
getMax方法,使用? extends T。 - 寫入數(shù)據(jù)的
addIntegers方法,使用? super T。 - 既讀又寫的場景,不使用通配符,直接使用具體的泛型類型。
1.6 泛型的常見問題與解決方案
1.6.1 泛型數(shù)組的創(chuàng)建問題
?? 不能直接創(chuàng)建泛型數(shù)組,例如 new T[10] 會編譯報錯。因為類型擦除后,JVM 無法確定數(shù)組的具體類型。
解決方案:
- 創(chuàng)建
Object數(shù)組,然后強制類型轉(zhuǎn)換。 - 使用反射創(chuàng)建泛型數(shù)組。
// 方式1:Object數(shù)組強制轉(zhuǎn)換
T[] array = (T[]) new Object[10];
// 方式2:反射創(chuàng)建泛型數(shù)組
public static <T> T[] createArray(Class<T> clazz, int size) {
return (T[]) Array.newInstance(clazz, size);
}
// 調(diào)用方式
String[] strArray = createArray(String.class, 5);1.6.2 泛型與異常的問題
?? 泛型不能用于異常類的定義和捕獲,例如 class GenericException<T> extends Exception 會編譯報錯。
原因:異常的處理是運行時機制,而泛型是編譯期機制,類型擦除后無法區(qū)分不同的泛型異常類型。
解決方案:如果需要傳遞泛型數(shù)據(jù),可以將泛型類型作為異常類的成員變量。
public class GenericException extends Exception {
private Object data;
public <T> GenericException(T data) {
this.data = data;
}
@SuppressWarnings("unchecked")
public <T> T getData() {
return (T) data;
}
}1.6.3 泛型與靜態(tài)方法的問題
?? 泛型類的靜態(tài)方法不能使用類的泛型參數(shù),因為靜態(tài)方法屬于類,在類加載時就已經(jīng)確定,而泛型參數(shù)是在創(chuàng)建對象時指定的。
解決方案:將靜態(tài)方法定義為泛型方法,使用自己的類型參數(shù)。
public class GenericClass<T> {
// 錯誤寫法:靜態(tài)方法使用類的泛型參數(shù)
// public static void method(T t) {}
// 正確寫法:使用泛型方法的類型參數(shù)
public static <E> void method(E e) {}
}1.7 實戰(zhàn)案例:泛型集合工具類封裝
1.7.1 需求分析
?? 封裝一個通用的泛型集合工具類 GenericCollectionUtil,提供以下功能:
- 集合去重:去除泛型集合中的重復(fù)元素,保留插入順序。
- 集合過濾:根據(jù)自定義規(guī)則過濾集合中的元素。
- 集合轉(zhuǎn)換:將一種類型的集合轉(zhuǎn)換為另一種類型的集合。
1.7.2 代碼實現(xiàn)
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Function;
public class GenericCollectionUtil {
/**
* 泛型集合去重,保留插入順序
* @param list 待去重的集合
* @param <T> 集合元素類型
* @return 去重后的集合
*/
public static <T> List<T> distinct(List<T> list) {
if (list == null || list.isEmpty()) {
return new ArrayList<>();
}
return new ArrayList<>(new LinkedHashSet<>(list));
}
/**
* 泛型集合過濾
* @param list 待過濾的集合
* @param predicate 過濾規(guī)則
* @param <T> 集合元素類型
* @return 過濾后的集合
*/
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
if (list == null || list.isEmpty() || predicate == null) {
return new ArrayList<>();
}
List<T> result = new ArrayList<>();
for (T t : list) {
if (predicate.test(t)) {
result.add(t);
}
}
return result;
}
/**
* 泛型集合轉(zhuǎn)換
* @param list 源集合
* @param function 轉(zhuǎn)換規(guī)則
* @param <T> 源元素類型
* @param <R> 目標元素類型
* @return 轉(zhuǎn)換后的集合
*/
public static <T, R> List<R> convert(List<T> list, Function<T, R> function) {
if (list == null || list.isEmpty() || function == null) {
return new ArrayList<>();
}
List<R> result = new ArrayList<>();
for (T t : list) {
R r = function.apply(t);
result.add(r);
}
return result;
}
// 測試方法
public static void main(String[] args) {
// 測試去重
List<Integer> numList = new ArrayList<>();
numList.add(1);
numList.add(2);
numList.add(1);
numList.add(3);
List<Integer> distinctList = GenericCollectionUtil.distinct(numList);
System.out.println("去重后的集合:" + distinctList);
// 測試過濾:過濾出偶數(shù)
List<Integer> evenList = GenericCollectionUtil.filter(distinctList, num -> num % 2 == 0);
System.out.println("過濾后的偶數(shù)集合:" + evenList);
// 測試轉(zhuǎn)換:將Integer轉(zhuǎn)換為String
List<String> strList = GenericCollectionUtil.convert(evenList, String::valueOf);
System.out.println("轉(zhuǎn)換后的字符串集合:" + strList);
}
}輸出結(jié)果
去重后的集合:[1, 2, 3]
過濾后的偶數(shù)集合:[2]
轉(zhuǎn)換后的字符串集合:[2]
1.7.3 案例總結(jié)
? 這個泛型工具類充分利用了泛型的特性,實現(xiàn)了對任意類型集合的通用操作。通過結(jié)合函數(shù)式接口(Predicate、Function),進一步提升了代碼的靈活性和復(fù)用性。在實際開發(fā)中,這樣的工具類可以大幅減少重復(fù)代碼,提升開發(fā)效率。
1.8 本章總結(jié)
- 泛型的核心是類型參數(shù)化,它在編譯期保證類型安全,運行時會發(fā)生類型擦除。
- 泛型的三種使用方式:泛型類、泛型接口、泛型方法,其中泛型方法的靈活性最高。
- 泛型的上下界限定可以限制類型參數(shù)的范圍,提升代碼的安全性。
- 通配符分為無界通配符(
?)、上界通配符(? extends T)和下界通配符(? super T),遵循 PECS 原則使用。 - 泛型的常見問題包括泛型數(shù)組創(chuàng)建、泛型與異常、泛型與靜態(tài)方法,需要通過特定方案解決。
- 泛型可以大幅提升代碼的復(fù)用性和安全性,是 JAVA 進階開發(fā)的必備技能。
到此這篇關(guān)于JAVA 泛型與通配符詳解從原理到實戰(zhàn)應(yīng)用的文章就介紹到這了,更多相關(guān)java泛型與通配符內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring中propagation的7種事務(wù)配置及說明
這篇文章主要介紹了Spring中propagation的7種事務(wù)配置及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
使用SpringBoot+nmap4j獲取端口信息的代碼詳解
這篇文章主要介紹了使用 SpringBoot + nmap4j 獲取端口信息,包括需求背景、nmap4j 的相關(guān)介紹、代碼說明(含測試代碼、改造后的代碼及參數(shù)說明),還提到了文件讀取方式和依賴引入方式,最終請求能獲取到數(shù)據(jù),需要的朋友可以參考下2025-01-01
Mybatis學(xué)習(xí)總結(jié)之mybatis使用建議
這篇文章主要介紹了Mybatis學(xué)習(xí)總結(jié)之mybatis使用建議的相關(guān)資料,非常具有參考借鑒價值,需要的朋友可以參考下2016-05-05
Java實現(xiàn)excel動態(tài)列導(dǎo)出的示例代碼
這篇文章主要為大家詳細介紹了如何使用Java實現(xiàn)excel動態(tài)列導(dǎo)出,文中的示例代碼講解詳細,具有一定的借鑒價值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03
Mybatis調(diào)用MySQL存儲過程的簡單實現(xiàn)
本篇文章主要介紹了Mybatis調(diào)用MySQL存儲過程的簡單實現(xiàn),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-04-04

