Java 泛型(Generics)是 Java 5 引入的重要特性之一,它允許在定義類、接口和方法時使用類型參數。泛型的核心思想是將類型由具體的數據類型推遲到使用時再確定,從而提升代碼的復用性和類型安全性。
1.泛型的基本概念
1. 什么是泛型?
泛型的本質是參數化類型。參數化類型是指帶有類型參數的類或接口在定義類或方法時不指定具體的類型,而是在實例化時傳入具體的類型。
- 泛型中不能寫基本數據類型
- 指定具體的泛型類型后,傳遞數據時,可以傳遞該類類型及其子類類型
- 如果不寫泛型,類型默認為Object
/*
List<E> 是 java.util 包下的接口。
E(Element)是類型參數,表示集合中元素的類型。
在代碼中你看到的 List<T> 實際上是程序員習慣使用的泛型變量名,與 List<E> 等價。
*/public interface List<E> extends Collection<E> { ... }/*
List<E> 是一個泛型接口(類型參數為 E)
List<String> 是一個參數化類型(String 是類型實參)
*/List<String> list = new ArrayList<>();
list.add("hello");
String str = list.get(0); // 無需強制轉換
2.為什么需要泛型?
在沒有泛型的情況下,集合類默認存儲的是?Object
?類型,這意味著你可以往集合中添加任何類型的對象,但取出來時需要手動強制轉換,容易引發?ClassCastException
。
示例:非泛型帶來的問題
List list = new ArrayList();
list.add("hello");
list.add(100); // 編譯通過String str = (String) list.get(1); // 運行時報錯:ClassCastException
?使用泛型后
泛型確保了編譯期的類型檢查,避免運行時類型轉換錯誤。
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(100); // 編譯錯誤,不能添加 Integer 類型
String str = list.get(0); // 不需要強制轉換
泛型允許我們編寫通用的類、接口和方法,而無需為每種數據類型重復實現相同邏輯。
示例:一個通用的容器類
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");
String s = stringBox.getItem(); // 直接獲取String類型Box<Integer> intBox = new Box<>();
intBox.setItem(123);
Integer i = intBox.getItem();
避免強制類型轉換(Avoid Casting)
在沒有泛型時,從集合中取出元素必須進行強制類型轉換,這不僅繁瑣,還可能出錯。
非泛型寫法
List list = new ArrayList();
list.add("hello");
String str = (String) list.get(0); // 強制轉換
泛型寫法
?泛型讓代碼更簡潔、清晰,減少出錯機會。
List<String> list = new ArrayList<>();
list.add("hello");String str = list.get(0); // 自動類型匹配
3. 泛型的優點
特性 | 描述 |
---|---|
類型安全 | 避免運行時 ClassCastException |
自動類型轉換 | 不需要手動強轉 |
代碼復用 | 使用泛型編寫通用邏輯 |
2.泛型的使用方式
1. 泛型類
通過在類名后加上?<T>
?來聲明一個泛型類,T
?是類型參數(Type Parameter)。可以表示屬性類型、方法的返回值類型、參數類型
創建該對象時,該標識確定類型
靜態方法不能使用類級別的泛型參數(如class MyClass<T>
中的T
),但可以定義自己的泛型參數。
/*
<>括號中的標識是任意設置的,用來表示類型參數,指代任何數據類型T :代表一般的任何類。E :代表 Element 元素的意思,或者 Exception 異常的意思。K :代表 Key 的意思。V :代表 Value 的意思,通常與 K 一起配合使用。S :代表 Subtype 的意思,文章后面部分會講解示意。
*/
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);
//多個泛型參數
public class Pair<K, V> {private K key;private V value;public Pair(K key, V value) {this.key = key;this.value = value;}public K getKey() { return key; }public V getValue() { return value; }
}
泛型類的繼承
子類也可以保留父類的泛型特性?
public class NumberBox<T extends Number> extends Box<T> {public double getDoubleValue() {return getValue().doubleValue();}
}
2. 泛型接口
T
?表示實體類型(如 User、Product)。ID
?表示主鍵類型(如 Long、String)。
public interface Repository<T, ID> {T findById(ID id);void save(T entity);void deleteById(ID id);
}
public interface Repository<T, ID> {
T findById(ID id);
void save(T t);
} public class UserRepository implements Repository<User, Long> {
@Override
public User findById(Long id) {
return null;
}@Override
public void save(User user) {}}
實現泛型接口并保留泛型
如果希望實現類也保持泛型特性,可以這樣做:
public class GenericRepository<T, ID> implements Repository<T, ID> {
@Override public T findById(ID id) {
// 泛型實現邏輯
return null;}
@Override public void save(T entity) {
// 泛型保存邏輯
}
@Override public void deleteById(ID id) {
// 泛型刪除邏輯}}
調用示例:
GenericRepository<User, Long> userRepository = new GenericRepository<>();
User user = userRepository.findById(1L);
userRepository.save(user);
3. 泛型方法
泛型方法的定義需要在返回類型前使用?<T>
?來聲明一個類型參數,其中?T
?是一個占位符,表示任意類型。
方法中參數類型不確定時,泛型方案選擇:
1.使用類名后面定義的泛型,所有方法都能用
2.在方法申明上定義自己的泛型,只有本方法能用
/*
<T>:聲明一個類型參數。
T value:接收任何類型的參數。
*/
public <T> void printValue(T value) {System.out.println(value);
}
printValue(10); // 整數
printValue("Hello"); // 字符串
printValue(3.14); // 浮點數
public class Utils {
public static <T> void printArray(T[] array) {
for (T item : array) {
System.out.println(item);}
}
}
// 調用
Utils.<String>printArray(new String[]{"A", "B"});
Utils.printArray(new Integer[]{1, 2, 3}); // 類型推斷
//泛型方法也可以返回泛型類型的數據
public <T> T getValue(T defaultValue) {return defaultValue;
}String result = getValue("Default");
Integer number = getValue(100);
?4.泛型類與泛型接口的區別
特性 | 泛型類 | 泛型接口 |
---|---|---|
定義方式 | class ClassName<T> | interface InterfaceName<T> |
主要用途 | 封裝通用的數據結構或行為 | 定義通用的行為規范 |
實現方式 | 直接實例化使用 | 需要被類實現后再使用 |
類型約束 | 可以通過?extends ?限制類型 | 同樣支持?extends ?約束 |
多類型參數 | 支持多個泛型參數 | 同樣支持多個泛型參數 |
3.泛型通配符
在 Java 泛型中,通配符(Wildcard)?是一種特殊的類型參數,用于表示未知的類型。它增強了泛型的靈活性,特別是在集合類的操作中非常有用。
1.為什么需要泛型通配符?
1.?泛型不具備多態性
在 Java 中,即使?Dog
?是?Animal
?的子類,List<Dog>
?并不是?List<Animal>
?的子類型。也就是說,泛型是不變的)。即泛型類型之間不繼承其參數類型的多態關系。
List<Dog> dogs = new ArrayList<>();
// List<Animal> animals = dogs; // 編譯錯誤!不能這樣賦值
2.為什么泛型不具備多態性?
1.?為了保證類型安全
如果允許?List<Dog>
?賦值給?List<Animal>
,就會帶來潛在的類型不安全風險。
假設允許這種賦值:
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs;
// 如果允許 animals.add(new Cat()); // 合法嗎?理論上可以,因為 Cat 是 Animal 子類Dog dog = dogs.get(0); // 錯誤 ClassCastException!
這會導致運行時異常,破壞了類型安全性。
因此,Java 在編譯期就禁止了這種行為。
3.對比數組的協變性
Java 中的數組是協變的(covariant),即:
Dog[] dogs = new Dog[3]; Animal[] animals = dogs; // 合法
但這其實也存在安全隱患,例如:
animals[0] = new Cat(); // 運行時報錯:ArrayStoreException
所以,Java 數組的協變性是在運行時進行類型檢查的,而泛型為了避免這種風險,在編譯期就禁止了這種操作。
如果你寫一個方法用于打印列表中的元素,你可能希望它能接受任何類型的?List
,比如?List<String>
、List<Integer>
?等。?那么就需要泛型具有多態性,由此就出現了通配符
4.通配符的類型?
1.上界通配符(只能進行只讀操作)
在 Java 泛型中,上界通配符?<? extends T>
?是一種特殊的泛型表達方式,用于表示某個類型是?T
?或其子類型。它提供了一種靈活的方式來處理具有繼承關系的泛型集合。
List<? extends T> list;
?
:表示未知類型。extends T
:表示該未知類型是?T
?或其子類。
List<? extends Number> numbers = new ArrayList<Integer>();
?合法賦值包括:
List<Integer>
List<Double>
List<AtomicInteger>
- 等等?
Number
?的子類列表
1.為什么使用上界通配符?
1.?允許讀取為父類型
你可以安全地將集合中的元素當作?T
?類型來讀取。
public void printNumbers(List<? extends Number> numbers) {for (Number number : numbers) {System.out.println(number);}
}
安全地讀取為?Number
,無論實際是?Integer
?還是?Double
。?
List<Integer> ints = List.of(1, 2);
List<Double> doubles = List.of(3.5, 4.5);printNumbers(ints); // ? 合法
printNumbers(doubles); // ? 合法
2.?避免類型不安全的寫入
雖然可以讀取,但不能向?<? extends T>
?集合中添加除?null
?外的任何對象。
List<? extends Number> list = new ArrayList<Integer>(); //
list.add(10); // 編譯錯誤!
list.add(null); // 合法(但幾乎無意義)
?2.為什么上界通配符只能進行只讀操作
List<Integer> integers = new ArrayList<>();
List<? extends Number> list = integers;// list.add(10); // 編譯錯誤!
// list.add(new Integer(5)); // 同樣不允許
雖然我們知道?list
?實際上是一個?List<Integer>
,并且可以添加?Integer
?類型的值,但編譯器無法確定?? extends Number
?到底是?Integer
、Double
?還是其他子類,所以為了保證類型安全,直接禁止寫入操作。
假設允許寫入會發生什么?
List<Integer> intList = new ArrayList<>();List<? extends Number> list = intList;
list.add(3.14); // 如果允許,會怎樣?Integer i = intList.get(0); // ClassCastException!
3.14
?是?Double
?類型。- 雖然它是?
Number
?的子類,但?intList
?只能存儲?Integer
。 - 此時如果允許寫入,就會破壞?
intList
?的類型一致性。
因此,Java 在編譯期就阻止了這種風險。
為什么可以添加 null?
list.add(null); // ?合法
null
?是所有引用類型的合法值。- 它不違反任何類型約束,因為?
null
?可以被當作任何類型來處理。
2.下界通配符(只寫不可讀)
在 Java 泛型中,下界通配符?<? super T>
?表示一個未知類型,它是?T
?或其任意父類。這種通配符用于增強泛型的靈活性,特別是在需要向集合中寫入數據時。
List<? super Integer> list;
? super Integer
:表示該列表可以是?Integer
、Number
?或?Object
?類型的列表。- 合法賦值包括:
List<Integer> integers = new ArrayList<>(); List<Number> numbers = new ArrayList<>();List<Object> objects = new ArrayList<>(); List<? super Integer> list1 = integers; // 允許List<? super Integer> list2 = numbers; // 允許 List<? super Integer> list3 = objects; // 允許
1.為什么下界通配符只能寫入,不能讀?
你可以安全地向?<? super T>
?集合中添加?T
?類型或其子類型的對象。
public void addIntegers(List<? super Integer> list) {
list.add(10); // 合法
list.add(new Integer(5)); // 合法}
原因:
- 編譯器知道?
? super Integer
?是?Integer
?的父類之一(如?Number
?或?Object
)。 - 所以你傳入一個?
Integer
,它一定能被接受(因為它是所有可能類型的子類)。
不能讀取為具體類型的原因:
雖然你可以寫入?Integer
,但你無法確定從集合中讀出的元素是什么類型。
List<? super Integer> list = new ArrayList<Number>();Object obj = list.get(0); // 只能讀作 Object // Integer i = list.get(0); // 編譯錯誤
原因:
list
?實際上可能是?List<Number>
?或?List<Object>
。- 所以編譯器不能保證返回的對象一定是?
Integer
,只能保證是?Object
?類型。
3.無限定通配符?
在 Java 泛型中,無限定通配符?<?>
?是一種特殊的泛型表達方式,表示“某種未知類型”。它用于定義一個可以接受任何泛型類型的集合或對象。
List<?> list;
?
:表示一個未知的類型。- 可以賦值為任何泛型類型的集合:
List<String> stringList = new ArrayList<>();List<Integer> intList = new ArrayList<>(); List<?> list1 = stringList; // 合法 List<?> list2 = intList; // 合法
1.為什么使用無限定通配符?
1.?適用于只讀操作(但只能讀作 Object)
你可以遍歷集合并讀取元素,但只能當作?Object
?類型處理:
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
調用示例:
printList(stringList); // 輸出字符串printList(intList); // 輸出整數
?注意:
你不能向?List<?>
?中添加任何非?null
?元素:
list.add("test"); // 編譯錯誤
list.add(10); // 編譯錯誤list.add(null); // 合法,但幾乎無意義
因為編譯器不知道??
?到底是什么類型,為了保證類型安全,禁止寫入。
4.通配符的對比
特性 | 無限定通配符?<?> | 上界通配符?<? extends T> | 下界通配符?<? super T> |
---|---|---|---|
表示類型 | 任意未知類型 | T ?或其子類 | T ?或其父類 |
讀取能力 | ? 只能讀作?Object | ? 可讀為?T | ? 可讀為?Object |
寫入能力 | ? 不允許(除?null ) | ? 不允許(除?null ) | ? 可寫入?T ?類型 |
使用場景 | 通用只讀操作 | 生產者(只讀) | 消費者(只寫) |
4.PECS 原則詳解
PECS(Producer Extends, Consumer Super)?是 Java 泛型中一個非常重要的設計原則,用于指導在使用泛型通配符時如何選擇合適的通配符類型,以確保類型安全和代碼靈活性。
1.PECS 的含義
角色 | 描述 | 使用的通配符 |
---|---|---|
Producer(生產者) | 只從集合中讀取數據 | <? extends T> |
Consumer(消費者) | 只向集合中寫入數據 | <? super T> |
? 簡單記憶:讀用 extends,寫用 super
2.詳細解釋
1.?Producer Extends
當你只需要從集合中讀取數據,并且希望集合可以接受?T
?或其子類的任意一種類型時,使用?<? extends T>
。
public void process(List<? extends Number> numbers) {for (Number number : numbers) {
System.out.println(number.doubleValue());}
}
調用示例:
List<Integer> ints = List.of(1, 2);
List<Double> doubles = List.of(3.5, 4.5);
process(ints); // 合法
process(doubles); // 合法
- ?優點:可以安全地讀取為?
Number
- ?缺點:不能寫入任何非?
null
?元素
2.?Consumer Super
當你只需要向集合中寫入數據,并希望集合能接受?T
?類型及其父類的集合時,使用?<? super T>
。
public void addNumbers(List<? super Integer> list) { list.add(10); list.add(20); }
調用示例:
List<Number> numbers = new ArrayList<>(); addNumbers(numbers); // ? 合法
- ?優點:可以安全地寫入?
Integer
?或其子類對象 - ?缺點:只能讀作?
Object
,無法還原為具體類型
3.為什么需要 PECS?
Java 泛型不具備多態性(Invariance),即即使?Dog
?是?Animal
?的子類,List<Dog>
?也不是?List<Animal>
?的子類。這導致我們在處理集合時面臨類型兼容性的挑戰。
通過使用通配符并遵循 PECS 原則,我們可以在保持類型安全的前提下,寫出更加通用、靈活的代碼。
4.典型應用示例
1.?生產者 + 消費者組合使用
public static <T> void copy(List<? super T> dest, List<? extends T> src) { for (T item : src) { dest.add(item); } }
src
?是生產者 → 使用?<? extends T>
dest
?是消費者 → 使用?<? super T>
調用示例:
List<Integer> source = List.of(1, 2, 3); List<Number> target = new ArrayList<>(); copy(target, source); // ? 合法
5.總結
內容 | 描述 |
---|---|
PECS 原則 | Producer Extends, Consumer Super |
核心思想 | 根據集合是“讀”還是“寫”來選擇合適的通配符 |
優勢 | 提高代碼復用性、增強類型安全性 |
限制 | 不能同時作為生產者和消費者 |
最佳實踐 | 在泛型集合操作中優先考慮通配符,避免直接使用具體泛型類型 |
理解并掌握 PECS 原則,是編寫高質量 Java 泛型代碼的關鍵所在。它幫助你在保持類型安全的同時,實現更通用、更靈活的設計。
?5.類型擦除(Type Erasure)
Java 的泛型類型擦除(Type Erasure)是 Java 泛型實現的核心機制之一,它指的是在編譯期間,泛型類型信息會被移除(擦除),以兼容非泛型的舊代碼(即 Java 5 之前的版本)。這意味著泛型只存在于編譯階段,在運行時并不存在具體的泛型類型。
示例:
盡管?List<String>
?和?List<Integer>
?在源碼中指定了不同的泛型類型,但在運行時它們的類型都是?ArrayList
,泛型信息被擦除了。
當把集合定義為string類型的時候,當數據添加在集合當中的時候,僅僅在門口檢查了一下數據是否符合String類型,? 如果是String類型,就添加成功,當添加成功以后,集合還是會把這些數據當做Object類型處理,當往外獲取的時候,集合在把他強轉String類型
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // true
1.類型擦除的過程
1.?替換所有類型參數為原始類型
- 類型參數如?
<T>
?被替換為其上界(upper bound)。 - 如果沒有指定上界,默認使用?
Object
。
例如:
public class Box<T> {private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {return value;}
}
編譯后相當于:
public class Box {
private Object value; p
ublic void setValue(Object value) {
this.value = value;
}
public Object getValue() {return value;}
}
2.?插入類型轉換代碼
編譯器會在適當的位置自動插入強制類型轉換,確保類型安全。
例如:
Box<String> box = new Box<>();
box.setValue("Hello");
String s = box.getValue(); // 編譯器自動插入 (String)box.getValue()
2.類型擦除的影響
影響 | 說明 |
---|---|
無法獲取泛型類型信息 | 運行時無法通過反射獲取?List<String> ?中的?String ?類型 |
不能實例化泛型類型 | new T() ?是非法的,因為運行時不知道?T ?是什么 |
不能創建泛型數組 | T[] array = new T[10]; ?是非法的 |
重載方法沖突 | 方法簽名在擦除后可能重復,導致編譯錯誤 |
示例:
public void process(List<String> list) {}
public void process(List<Integer> list) {} // 編譯錯誤:方法簽名沖突
3.如何繞過類型擦除(獲取泛型信息)
雖然 Java 擦除了泛型信息,但在某些情況下可以通過反射獲取泛型類型信息,前提是該泛型類型是在聲明時明確指定的(不是變量類型)。
示例:獲取父類的泛型類型
abstract class Base<T> {
abstract T get();
}
class StringSub extends Base<String> {@Override String get() { return null; } } // 獲取泛型類型
Type type = StringSub.class.getGenericSuperclass();
if (type instanceof ParameterizedType pt) {
Type actualType = pt.getActualTypeArguments()[0];
System.out.println(actualType); // 輸出: class java.lang.String
}
5.泛型與繼承
- 子類可以繼承父類并指定泛型類型。
- 子類也可以繼續保留泛型參數。
class Animal {}
class Dog extends Animal {}
class Cage<T> {
private T animal;
public void set(T animal) {
this.animal = animal;}
public T get() {
return animal;
}
}
class DogCage extends Cage<Dog> { // 此處 T 已經固定為 Dog }
6.泛型常見錯誤
錯誤示例 | 原因 |
---|---|
List<int> | 泛型不能使用基本類型,應使用?List<Integer> |
new T() | 編譯器不知道?T ?是什么類型 |
new List<String>[] | 泛型數組不可創建 |
if (obj instanceof List<String>) | 泛型被擦除,無法判斷 |
7.Java 明確禁止創建具體泛型參數類型的數組
?在 Java 中,泛型數組(如?List<Dog>[]
)與單個泛型對象(如?List<Integer>
)的創建規則完全不同,核心區別在于?數組的協變性(Covariance)?和?泛型的不變性(Invariance)。以下是詳細解釋:
?類型擦除
Java 的泛型是通過編譯時的類型檢查實現的,但在運行時,泛型信息會被擦除。例如:
List<Dog> list1 = new ArrayList<>();
List<Cat> list2 = new ArrayList<>();
// 運行時 list1 和 list2 都是 List 類型
所以當你嘗試創建一個?ArrayList<Dog>[10]
?時,運行時實際上只能看到?ArrayList[]
,而無法區分里面存儲的是?List<Dog>
?還是?List<Cat>
。
數組協變性
Java 數組是協變的(covariant),即:
String[] strings = new String[10]; Object[] objects = strings; // 合法
1. 為什么?List<Dog>[] listArray = new ArrayList<Dog>[10];
?不可以?
1.1 核心原因:泛型數組的類型安全問題
Java 的泛型是通過?類型擦除(Type Erasure)?實現的,即泛型信息在運行時被移除。而?數組在運行時保留類型信息,并且支持協變性(Dog[]
?是?Animal[]
?的子類型),這會導致類型不安全。
示例:泛型數組的潛在風險
// 假設允許創建泛型數組
List<Dog>[] listArray = new ArrayList<Dog>[10];// 插入錯誤類型的 List(Dog 是 Animal 的子類)List<Animal> animalList = new ArrayList<>();listArray[0] = animalList; // 編譯通過,但實際類型不匹配!
// 后續訪問時可能出現 ClassCastException
Dog dog = listArray[0].get(0); // 如果 animalList 中有 Cat,此處拋出異常
- 問題:
List<Dog>
?和?List<Animal>
?沒有繼承關系(泛型是不變的),但數組的協變性允許將?List<Animal>
?賦值給?List<Dog>[]
,導致運行時類型不一致。
1.2 Java 的設計限制
Java 禁止直接創建泛型數組,因為:
- 運行時無法驗證數組元素的類型(類型擦除導致)。
- 數組的協變性與泛型的不變性沖突,可能引發類型不安全。
2. 為什么?List<Integer> list01 = new ArrayList<>();
?可以?
2.1 鉆石操作符(Diamond Operator)
- Java 7 引入:允許在實例化泛型類時省略類型參數,編譯器根據上下文自動推斷類型。
- 示例:
List<Integer> list01 = new ArrayList<>(); // 合法
- 編譯器推斷?
ArrayList<>
?為?ArrayList<Integer>
。 - 等價于顯式聲明:
List<Integer> list01 = new ArrayList<Integer>();
- 編譯器推斷?
2.2 為什么這是安全的?
- 單個對象的類型是固定的:
ArrayList<Integer>
?僅存儲?Integer
?類型元素,不會出現多態賦值問題。 - 泛型的不變性不影響單個對象:無需考慮數組的協變性問題。
3. 關鍵區別總結
復制
特性 | 泛型數組(如?List<Dog>[] ) | 單個泛型對象(如?List<Integer> ) |
---|---|---|
類型檢查時機 | 運行時檢查(數組保留類型信息) | 編譯時檢查(泛型通過類型擦除實現) |
協變性 | 支持協變性(Dog[] ?是?Animal[] ?的子類型) | 不支持協變性(List<Dog> ?與?List<Animal> ?無繼承關系) |
類型安全 | 無法保證(可能插入錯誤類型) | 完全保證(編譯器強制類型匹配) |
Java 允許性 | ?不允許(編譯警告或錯誤) | 允許(鉆石操作符合法) |
4. 替代方案:如何安全創建泛型數組?
如果需要存儲泛型集合的數組,推薦以下方式:
4.1 使用通配符數組
List<?>[] listArray = new ArrayList<?>[10]; // 安全創建
listArray[0] = new ArrayList<Dog>();
listArray[1] = new ArrayList<Animal>(); // 合法,但只能讀取(不能添加元素)
- 限制:不能向?
List<?>
?中添加元素(除了?null
)。
4.2 使用嵌套集合
List<List<Dog>> listList = new ArrayList<>();listList.add(new ArrayList<>()); // 安全
- 優點:完全利用泛型的類型安全性,避免數組的協變性問題。
5. 總結
- 泛型數組不可創建:由于類型擦除和數組的協變性,Java 禁止直接創建泛型數組(如?
List<Dog>[]
),以避免運行時類型不安全。 - 單個泛型對象可創建:使用鉆石操作符?
<>
(Java 7+)可安全創建單個泛型對象(如?List<Integer>
),因為類型推斷在編譯期完成,且不涉及多態賦值。
通過理解數組和泛型的設計差異,可以更安全地編寫 Java 代碼,避免潛在的類型錯誤。