Skip to main content

Java 泛型(Generics)详解

Java泛型是JDK 5.0引入的重要特性,它提供了编译时类型安全检查,允许在编译时检测到不正确的类型使用。泛型的主要目的是实现"类型参数化",让代码更加灵活、安全,同时避免类型转换异常。

核心价值

泛型 = 类型安全 + 代码重用 + 性能优化 + 编译时检查

  • 🔒 类型安全:在编译时进行类型检查,减少运行时类型错误
  • ♻️ 代码重用:编写适用于多种类型的通用代码,提高代码复用性
  • 性能优化:避免类型转换的开销,提升运行时性能
  • 🧩 API设计:设计灵活且类型安全的API,增强代码健壮性
  • 📝 自文档化:泛型参数提供了类型信息,增强代码可读性

1. 泛型基础概念

1.1 什么是泛型?

泛型是一种参数化类型的概念,它允许在定义类、接口和方法时使用类型参数。这些类型参数在使用时会被具体的类型替换,从而提供类型安全。

泛型类定义
java
1public class Box<T> {
2 private T content;
3
4 public void set(T content) {
5 this.content = content;
6 }
7
8 public T get() {
9 return content;
10 }
11}

1.2 泛型的好处

好处说明示例
类型安全编译时检查类型匹配,避免运行时异常List<String> 只能存储String类型
消除类型转换无需手动进行类型转换String s = list.get(0); 直接获取String
代码复用一个泛型类可以处理多种类型Box<T> 可以存储任何类型
编译时检查在编译时发现类型错误尝试存储错误类型时编译失败
不使用泛型的代码
java
1// 没有泛型 - 可能出错
2List list = new ArrayList();
3list.add("Hello");
4list.add(42); // 可以添加任何类型,类型不安全
5String s = (String) list.get(1); // 运行时异常!ClassCastException
运行时错误

上面的代码在编译时不会报错,但在运行时会抛出 ClassCastException,因为尝试将 Integer 转换为 String。 这种错误只能在运行时被发现,增加了调试难度。

2. 泛型类型参数

2.1 类型参数命名约定

Java泛型使用类型参数来表示类型,这些参数通常使用单个大写字母命名,遵循以下约定:

类型参数含义示例
EElement(元素)Collection<E>
TType(类型)Box<T>
KKey(键)Map<K,V>
VValue(值)Map<K,V>
NNumber(数字)Number<N>
SSource(源)Function<S,T>

2.2 多个类型参数

泛型类可以定义多个类型参数,用逗号分隔:

多个类型参数
java
1public class Pair<K, V> {
2 private K key;
3 private V value;
4
5 public Pair(K key, V value) {
6 this.key = key;
7 this.value = value;
8 }
9
10 public K getKey() { return key; }
11 public V getValue() { return value; }
12
13 public void setKey(K key) { this.key = key; }
14 public void setValue(V value) { this.value = value; }
15}

使用示例

Pair类的使用
java
1// 创建String-Integer对
2Pair<String, Integer> pair1 = new Pair<>("Age", 25);
3String key = pair1.getKey(); // String类型
4Integer value = pair1.getValue(); // Integer类型
5
6// 创建Integer-String对
7Pair<Integer, String> pair2 = new Pair<>(1, "One");
8Integer key2 = pair2.getKey(); // Integer类型
9String value2 = pair2.getValue(); // String类型
类型参数命名

虽然可以使用任何标识符作为类型参数,但建议遵循Java的命名约定,这样代码更易读、更专业。

3. 泛型方法

3.1 泛型方法定义

泛型方法是在方法级别使用泛型,它可以在非泛型类中定义,也可以在泛型类中定义。

基本语法

泛型方法定义
java
1public class Utils {
2 // 泛型方法:在返回类型前声明类型参数
3 public static <T> void printArray(T[] array) {
4 for (T element : array) {
5 System.out.print(element + " ");
6 }
7 System.out.println();
8 }
9
10 // 泛型方法:返回泛型类型
11 public static <T> T getFirst(T[] array) {
12 if (array.length > 0) {
13 return array[0];
14 }
15 return null;
16 }
17}

使用泛型方法

泛型方法调用
java
1// 调用泛型方法
2String[] strings = {"Hello", "World"};
3Integer[] numbers = {1, 2, 3, 4, 5};
4
5Utils.printArray(strings); // 输出: Hello World
6Utils.printArray(numbers); // 输出: 1 2 3 4 5
7
8String firstString = Utils.getFirst(strings); // String类型
9Integer firstNumber = Utils.getFirst(numbers); // Integer类型

3.2 泛型方法与泛型类的区别

特性泛型类泛型方法
声明位置类名后方法返回类型前
作用范围整个类单个方法
类型参数实例化时确定调用时推断
使用方式new Box<String>()Utils.<String>method()
类型推断

Java编译器通常能够自动推断泛型方法的类型参数,所以通常不需要显式指定:

java
1// 编译器自动推断T为String
2Utils.printArray(new String[]{"Hello"});
3
4// 显式指定类型参数(通常不需要)
5Utils.<String>printArray(new String[]{"Hello"});

4. 类型擦除(Type Erasure)

Java泛型是通过类型擦除实现的,这意味着在运行时,泛型信息会被擦除,所有的泛型类型都被转换为它们的原始类型(raw type)。

4.1 类型擦除机制

类型擦除是Java泛型实现的核心机制,编译器会在编译时移除所有泛型类型信息,这种设计主要是为了向后兼容性。

类型擦除示例
java
1// 编译时:泛型类型
2List<String> stringList = new ArrayList<>();
3List<Integer> intList = new ArrayList<>();
4
5// 运行时:类型被擦除,都变成List
6// 实际类型:List stringList = new ArrayList();
7// 实际类型:List intList = new ArrayList();

验证类型擦除

类型擦除验证
java
1public class TypeErasureDemo {
2 public static void main(String[] args) {
3 List<String> stringList = new ArrayList<>();
4 List<Integer> intList = new ArrayList<>();
5
6 // 检查运行时类型
7 System.out.println(stringList.getClass()); // class java.util.ArrayList
8 System.out.println(intList.getClass()); // class java.util.ArrayList
9
10 // 类型擦除后,两个列表的类对象是相同的
11 System.out.println(stringList.getClass() == intList.getClass()); // true
12 }
13}

4.2 类型擦除的影响

4.2.1 无法创建泛型数组

泛型数组限制
java
1// 编译错误:不能创建泛型数组
2// T[] array = new T[10]; // 错误!
3
4// 解决方案1:使用Object数组,然后转换
5public class GenericArray<T> {
6 private Object[] array;
7
8 public GenericArray(int size) {
9 array = new Object[size];
10 }
11
12 @SuppressWarnings("unchecked")
13 public T get(int index) {
14 return (T) array[index];
15 }
16
17 public void set(int index, T element) {
18 array[index] = element;
19 }
20}

4.2.2 无法使用instanceof检查泛型类型

instanceof限制
java
1public class TypeCheckDemo {
2 public static <T> void checkType(List<T> list) {
3 // 编译错误:不能使用instanceof检查泛型类型
4 // if (list instanceof List<String>) { } // 错误!
5
6 // 正确的检查方式
7 if (list instanceof List) {
8 System.out.println("这是一个List");
9 }
10 }
11}
类型擦除的限制

由于类型擦除,Java泛型在运行时无法获取具体的类型信息,这限制了某些高级泛型操作的使用。

5. 通配符(Wildcards)

泛型通配符提供了更灵活的类型参数使用方式,能够更好地支持多态性和子类型关系。

5.1 无界通配符(Unbounded Wildcard)

无界通配符使用 ? 表示,表示可以接受任何类型。它常用于不依赖于类型参数的泛型代码中。

基本用法

无界通配符
java
1public class WildcardDemo {
2 // 接受任何类型的List
3 public static void printList(List<?> list) {
4 for (Object item : list) {
5 System.out.print(item + " ");
6 }
7 System.out.println();
8 }
9
10 public static void main(String[] args) {
11 List<String> stringList = Arrays.asList("Hello", "World");
12 List<Integer> intList = Arrays.asList(1, 2, 3);
13
14 printList(stringList); // 输出: Hello World
15 printList(intList); // 输出: 1 2 3
16 }
17}

无界通配符的限制

无界通配符限制
java
1public class WildcardLimitations {
2 public static void addElement(List<?> list) {
3 // 编译错误:不能添加元素到无界通配符列表
4 // list.add("Hello"); // 错误!
5
6 // 只能添加null
7 list.add(null); // 允许
8
9 // 可以读取元素,但类型是Object
10 Object item = list.get(0);
11 }
12}

5.2 上界通配符(Upper Bounded Wildcard)

上界通配符使用 ? extends T 表示,表示类型必须是T或其子类型。

基本用法

上界通配符
java
1public class UpperBoundedWildcard {
2 // 接受Number及其子类型的List
3 public static double sumOfList(List<? extends Number> list) {
4 double sum = 0.0;
5 for (Number number : list) {
6 sum += number.doubleValue();
7 }
8 return sum;
9 }
10
11 public static void main(String[] args) {
12 List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);
13 List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
14
15 System.out.println(sumOfList(intList)); // 输出: 15.0
16 System.out.println(sumOfList(doubleList)); // 输出: 6.6
17 }
18}

上界通配符的限制

上界通配符限制
java
1public class UpperBoundedLimitations {
2 public static void addNumber(List<? extends Number> list) {
3 // 编译错误:不能添加元素到上界通配符列表
4 // list.add(42); // 错误!
5 // list.add(3.14); // 错误!
6
7 // 只能添加null
8 list.add(null); // 允许
9
10 // 可以读取元素,类型是Number
11 Number first = list.get(0);
12 }
13}

5.3 下界通配符(Lower Bounded Wildcard)

下界通配符使用 ? super T 表示,表示类型必须是T或其父类型。

基本用法

下界通配符
java
1public class LowerBoundedWildcard {
2 // 接受Integer及其父类型的List
3 public static void addIntegers(List<? super Integer> list) {
4 list.add(1);
5 list.add(2);
6 list.add(3);
7 }
8
9 public static void main(String[] args) {
10 List<Number> numberList = new ArrayList<>();
11 List<Object> objectList = new ArrayList<>();
12
13 addIntegers(numberList); // 可以添加Integer到Number列表
14 addIntegers(objectList); // 可以添加Integer到Object列表
15
16 System.out.println(numberList); // [1, 2, 3]
17 System.out.println(objectList); // [1, 2, 3]
18 }
19}

下界通配符的特点

下界通配符特点
java
1public class LowerBoundedCharacteristics {
2 public static void processList(List<? super Integer> list) {
3 // 可以添加Integer及其子类型
4 list.add(42);
5 list.add(100);
6
7 // 可以读取元素,但类型是Object
8 Object item = list.get(0);
9
10 // 不能读取为Integer(可能不安全)
11 // Integer number = list.get(0); // 错误!
12 }
13}
通配符类型语法读取操作写入操作应用场景
无界通配符List<?>只能作为Object读取只能添加null只读取不关心具体类型的场景
上界通配符List<? extends T>可以作为T类型读取只能添加null从列表中读取T类型元素的场景
下界通配符List<? super T>只能作为Object读取可以添加T及其子类型向列表中添加T类型元素的场景

6. 泛型约束与边界

6.1 类型边界(Type Bounds)

类型边界用于限制泛型类型参数的范围,确保类型参数满足特定条件。

上界类型边界

上界类型边界
java
1public class NumberBox<T extends Number> {
2 private T number;
3
4 public NumberBox(T number) {
5 this.number = number;
6 }
7
8 public T getNumber() {
9 return number;
10 }
11
12 // 可以调用Number的方法
13 public double getDoubleValue() {
14 return number.doubleValue();
15 }
16
17 public int getIntValue() {
18 return number.intValue();
19 }
20}

使用上界类型边界

上界边界使用
java
1public class BoundedTypeDemo {
2 public static void main(String[] args) {
3 // 可以使用Number及其子类型
4 NumberBox<Integer> intBox = new NumberBox<>(42);
5 NumberBox<Double> doubleBox = new NumberBox<>(3.14);
6
7 System.out.println(intBox.getDoubleValue()); // 42.0
8 System.out.println(doubleBox.getDoubleValue()); // 3.14
9
10 // 编译错误:String不是Number的子类型
11 // NumberBox<String> stringBox = new NumberBox<>("Hello"); // 错误!
12 }
13}

多重边界

多重边界
java
1public class MultipleBounds<T extends Number & Comparable<T>> {
2 private T value;
3
4 public MultipleBounds(T value) {
5 this.value = value;
6 }
7
8 public T getValue() {
9 return value;
10 }
11
12 // 可以使用Number和Comparable的方法
13 public double getDoubleValue() {
14 return value.doubleValue();
15 }
16
17 public int compareTo(T other) {
18 return value.compareTo(other);
19 }
20}

6.2 递归类型边界

递归类型边界用于表示类型参数必须与自身相关。

递归类型边界
java
1public class RecursiveTypeBound {
2 // T必须实现Comparable<T>,即可以与自身比较
3 public static <T extends Comparable<T>> T max(T a, T b) {
4 if (a.compareTo(b) > 0) {
5 return a;
6 } else {
7 return b;
8 }
9 }
10
11 public static void main(String[] args) {
12 // Integer实现了Comparable<Integer>
13 Integer maxInt = max(10, 20);
14 System.out.println(maxInt); // 20
15
16 // String实现了Comparable<String>
17 String maxString = max("Hello", "World");
18 System.out.println(maxString); // World
19 }
20}

7. 泛型接口与实现

7.1 泛型接口

泛型接口允许接口使用类型参数,实现类可以选择具体的类型或保持泛型。

基本泛型接口

泛型接口定义
java
1public interface Container<T> {
2 void add(T element);
3 T get(int index);
4 int size();
5 boolean isEmpty();
6}

实现泛型接口

泛型接口实现
java
1// 实现为具体类型
2public class StringContainer implements Container<String> {
3 private List<String> elements = new ArrayList<>();
4
5 @Override
6 public void add(String element) {
7 elements.add(element);
8 }
9
10 @Override
11 public String get(int index) {
12 return elements.get(index);
13 }
14
15 @Override
16 public int size() {
17 return elements.size();
18 }
19
20 @Override
21 public boolean isEmpty() {
22 return elements.isEmpty();
23 }
24}
25
26// 保持泛型的实现
27public class GenericContainer<T> implements Container<T> {
28 private List<T> elements = new ArrayList<>();
29
30 @Override
31 public void add(T element) {
32 elements.add(element);
33 }
34
35 @Override
36 public T get(int index) {
37 return elements.get(index);
38 }
39
40 @Override
41 public int size() {
42 return elements.size();
43 }
44
45 @Override
46 public boolean isEmpty() {
47 return elements.isEmpty();
48 }
49}

7.2 泛型继承

泛型类可以继承其他泛型类,形成复杂的泛型层次结构。

泛型继承
java
1// 基础泛型类
2public class Box<T> {
3 protected T content;
4
5 public Box(T content) {
6 this.content = content;
7 }
8
9 public T getContent() {
10 return content;
11 }
12
13 public void setContent(T content) {
14 this.content = content;
15 }
16}
17
18// 继承泛型类,保持泛型
19public class NumberBox<T extends Number> extends Box<T> {
20 public NumberBox(T content) {
21 super(content);
22 }
23
24 public double getDoubleValue() {
25 return content.doubleValue();
26 }
27}
28
29// 继承泛型类,固定类型
30public class StringBox extends Box<String> {
31 public StringBox(String content) {
32 super(content);
33 }
34
35 public int getLength() {
36 return content.length();
37 }
38}

8. 泛型最佳实践

8.1 命名约定

遵循Java泛型的命名约定,使代码更易读:

命名约定
java
1// 推荐的命名
2public class Cache<K, V> { }
3public class Repository<T> { }
4public class Service<E> { }
5
6// 避免的命名
7public class Cache<Key, Value> { } // 太长
8public class Repository<Type> { } // 不够通用
9public class Service<Element> { } // 太长

8.2 类型边界使用

合理使用类型边界,避免过度约束:

类型边界最佳实践
java
1// 好的做法:适当的约束
2public class NumberProcessor<T extends Number> {
3 public double process(T number) {
4 return number.doubleValue();
5 }
6}
7
8// 避免:过度约束
9public class NumberProcessor<T extends Number & Comparable<T> & Serializable> {
10 // 这限制了太多类型
11}
12
13// 好的做法:使用通配符
14public static double sum(List<? extends Number> numbers) {
15 double sum = 0.0;
16 for (Number num : numbers) {
17 sum += num.doubleValue();
18 }
19 return sum;
20}

8.3 避免原始类型

始终使用泛型,避免使用原始类型:

避免原始类型
java
1// 错误:使用原始类型
2List list = new ArrayList();
3list.add("Hello");
4list.add(42); // 可以添加任何类型
5
6// 正确:使用泛型
7List<String> stringList = new ArrayList<>();
8stringList.add("Hello");
9// stringList.add(42); // 编译错误,类型安全

8.4 泛型方法设计

设计泛型方法时,考虑类型推断和易用性:

泛型方法设计
java
1public class CollectionUtils {
2 // 好的设计:类型推断友好
3 public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
4 List<T> result = new ArrayList<>();
5 for (T item : list) {
6 if (predicate.test(item)) {
7 result.add(item);
8 }
9 }
10 return result;
11 }
12
13 // 使用示例
14 public static void main(String[] args) {
15 List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
16
17 // 类型推断自动工作
18 List<String> filtered = filter(names, name -> name.startsWith("A"));
19 System.out.println(filtered); // [Alice]
20 }
21}

9. 常见问题与解决方案

9.1 泛型数组问题

泛型数组解决方案
java
1public class GenericArraySolutions {
2 // 方案1:使用Object数组
3 public static <T> T[] createArray1(int size) {
4 @SuppressWarnings("unchecked")
5 T[] array = (T[]) new Object[size];
6 return array;
7 }
8
9 // 方案2:使用反射
10 public static <T> T[] createArray2(Class<T> clazz, int size) {
11 @SuppressWarnings("unchecked")
12 T[] array = (T[]) Array.newInstance(clazz, size);
13 return array;
14 }
15
16 // 方案3:使用泛型集合
17 public static <T> List<T> createList(int size) {
18 return new ArrayList<>(size);
19 }
20}

9.2 类型擦除的变通方案

类型擦除变通方案
java
1public class TypeErasureWorkarounds {
2 // 使用Class对象保存类型信息
3 public static class TypeAwareBox<T> {
4 private T content;
5 private Class<T> type;
6
7 public TypeAwareBox(T content, Class<T> type) {
8 this.content = content;
9 this.type = type;
10 }
11
12 public T getContent() {
13 return content;
14 }
15
16 public Class<T> getType() {
17 return type;
18 }
19
20 public boolean isInstance(Object obj) {
21 return type.isInstance(obj);
22 }
23 }
24}

10. 面试题精选

10.1 基础概念题

Q: 什么是Java泛型?它解决了什么问题?

A: Java泛型是JDK 5.0引入的特性,它提供了编译时类型安全检查。主要解决了以下问题:

  • 类型安全:编译时检查类型匹配,避免运行时异常
  • 消除类型转换:无需手动进行类型转换
  • 代码复用:一个泛型类可以处理多种类型
  • 编译时检查:在编译时发现类型错误

Q: 什么是类型擦除?它有什么影响?

A: 类型擦除是Java泛型的实现机制,在运行时泛型信息会被擦除,所有泛型类型都转换为原始类型。主要影响包括:

  • 无法创建泛型数组:T[] array = new T[10] 编译错误
  • 无法使用instanceof检查泛型类型:list instanceof List<String> 编译错误
  • 运行时无法获取具体的泛型类型信息

10.2 通配符题

Q: 解释 ? extends T? super T 的区别?

A:

  • ? extends T(上界通配符):表示类型必须是T或其子类型,可以读取元素(类型为T),但不能添加元素
  • ? super T(下界通配符):表示类型必须是T或其父类型,可以添加元素(类型为T),但读取时类型为Object

Q: 什么时候使用无界通配符 ?

A: 当方法只关心集合的结构(如大小、是否为空),而不关心元素类型时使用。例如:

java
1public static boolean isEmpty(Collection<?> collection) {
2 return collection == null || collection.isEmpty();
3}

10.3 实践题

Q: 设计一个泛型缓存类,支持键值对存储

A:

java
1public class GenericCache<K, V> {
2 private Map<K, V> cache = new HashMap<>();
3
4 public void put(K key, V value) {
5 cache.put(key, value);
6 }
7
8 public V get(K key) {
9 return cache.get(key);
10 }
11
12 public boolean containsKey(K key) {
13 return cache.containsKey(key);
14 }
15
16 public void remove(K key) {
17 cache.remove(key);
18 }
19
20 public int size() {
21 return cache.size();
22 }
23
24 public void clear() {
25 cache.clear();
26 }
27}

Q: 实现一个泛型方法,找出数组中的最大值

A:

java
1public static <T extends Comparable<T>> T findMax(T[] array) {
2 if (array == null || array.length == 0) {
3 throw new IllegalArgumentException("Array cannot be null or empty");
4 }
5
6 T max = array[0];
7 for (int i = 1; i < array.length; i++) {
8 if (array[i].compareTo(max) > 0) {
9 max = array[i];
10 }
11 }
12 return max;
13}
泛型学习要点
  1. 理解类型擦除:这是Java泛型的核心机制
  2. 掌握通配符:合理使用三种通配符类型
  3. 遵循最佳实践:避免原始类型,合理使用类型边界
  4. 实践应用:在实际项目中应用泛型提高代码质量

11. 总结

泛型学习路径

学习Java泛型的建议路径:

  1. 掌握基础语法和概念
  2. 理解类型擦除机制
  3. 学习通配符用法
  4. 掌握PECS原则
  5. 实践泛型类和方法的设计

通过合理使用泛型,可以编写更加通用、安全和可维护的代码。虽然类型擦除带来了一些限制,但总体而言,泛型给Java带来的好处远超过其缺点。对泛型的深入理解,是成为高级Java开发者的必备技能。

类型安全: 在编译时捕获类型错误,避免运行时异常
代码复用: 一套代码可以处理多种不同类型
消除类型转换: 减少冗余的类型转换代码
API设计: 使API更加清晰、类型安全
性能: 编译后的代码和手动类型转换的代码性能相当


通过本章的学习,你应该已经掌握了Java泛型的核心概念、语法规则和最佳实践。泛型是Java中非常重要的特性,它不仅能提高代码的类型安全性,还能增强代码的可读性和可维护性。在实际开发中,合理使用泛型可以避免很多运行时错误,让代码更加健壮。

12. 面试题精选

12.1 什么是Java泛型?泛型有什么优点?

答案: Java泛型是JDK 5引入的特性,允许在定义类、接口和方法时使用类型参数,这些参数在使用时会被具体类型替换。

泛型的主要优点:

  • 类型安全:在编译时检查类型错误,避免运行时ClassCastException
  • 消除类型转换:不需要显式转换对象类型,代码更加简洁
  • 实现通用算法:可以编写适用于多种类型的通用代码
  • 代码复用:减少因类型不同而导致的重复代码
  • 更好的API设计:提供了类型约束,使API更加直观

12.2 什么是类型擦除?为什么Java泛型使用类型擦除?

答案: 类型擦除是Java泛型的基本实现机制,它指的是编译器在编译时会擦除所有泛型类型相关的信息,替换为原始类型(通常是Object或上界类型)。

Java泛型使用类型擦除的原因:

  1. 向后兼容性:保证泛型代码可以与Java 5之前的代码无缝协作
  2. 避免运行时开销:不需要在运行时维护额外的类型信息
  3. JVM限制:不需要修改Java虚拟机,简化了实现

类型擦除的主要后果:

  • 泛型信息在运行时不可用
  • 无法使用instanceof检查泛型类型
  • 无法创建泛型类型的数组
  • 静态上下文中不能引用类型参数

12.3 解释泛型中的通配符,以及PECS原则

答案: 通配符是Java泛型中的特殊符号,用于表示未知类型,有三种形式:

  1. 无界通配符(?): List<?>表示可以是任何类型的列表
  2. 上界通配符(? extends T): List<? extends Number>表示Number或其子类的列表
  3. 下界通配符(? super T): List<? super Integer>表示Integer或其父类的列表

PECS原则(Producer-Extends, Consumer-Super):

  • 当你的泛型类是生产者(提供数据)时,使用? extends T
  • 当你的泛型类是消费者(接收数据)时,使用? super T

例如:

java
1// 从列表读取数据(生产者),使用extends
2public void readFrom(List<? extends Number> list) {
3 Number n = list.get(0); // 安全,知道是Number或子类
4}
5
6// 向列表写入数据(消费者),使用super
7public void writeTo(List<? super Integer> list) {
8 list.add(42); // 安全,知道list可以接收Integer
9}

12.4 泛型类型参数的命名约定是什么?常见的类型参数名称代表什么?

答案: Java泛型类型参数通常使用单个大写字母表示。最常见的类型参数命名约定:

  • E: Element(元素),通常用于集合类
  • T: Type(类型),最常用的泛型类型参数
  • K: Key(键),常用于Map的键类型
  • V: Value(值),常用于Map的值类型
  • N: Number(数字),表示数值类型
  • S, U, V: 第2、3、4个类型参数,当需要多个泛型类型时使用

例如:

java
1// T代表任意类型
2public class Box<T> { }
3
4// K代表键类型,V代表值类型
5public interface Map<K, V> { }
6
7// E代表元素类型
8public interface List<E> { }

12.5 如何解决泛型数组创建的问题?

答案: 由于类型擦除,在Java中无法直接创建泛型数组(如new T[10])。解决这个问题有以下几种方法:

  1. 使用Object数组加类型转换:
java
1public class GenericArray<T> {
2 private T[] array;
3
4 @SuppressWarnings("unchecked")
5 public GenericArray(int size) {
6 // 创建Object数组,然后强制类型转换
7 array = (T[]) new Object[size];
8 }
9}
  1. 使用反射创建数组:
java
1public class GenericArray<T> {
2 private T[] array;
3
4 @SuppressWarnings("unchecked")
5 public GenericArray(Class<T> type, int size) {
6 // 使用反射API创建特定类型的数组
7 array = (T[]) Array.newInstance(type, size);
8 }
9}
  1. 使用ArrayList代替数组:
java
1public class GenericCollection<T> {
2 private List<T> list;
3
4 public GenericCollection(int size) {
5 list = new ArrayList<>(size);
6 }
7}

每种方法都有其优缺点,选择取决于具体需求和使用场景。

参与讨论