泛型常见问题
泛型十问
泛型在使用中有很多容易混淆的地方,这里整理了最常见的 10 个问题。
Q1:泛型和 Object 数组有什么区别?
Object 数组可以装任何东西,但取出来需要强制转型:
java
// Object 数组:运行时不检查
Object[] arr = new Object[3];
arr[0] = "hello";
arr[1] = 123; // 鬼都能塞进去
String s = (String) arr[1]; // 运行时 ClassCastException
// 泛型:编译时就检查
String[] arr2 = new String[3];
arr2[0] = "hello";
arr2[1] = 123; // 编译错误!连编译都过不了结论:泛型把「运行时的错误」提前到「编译期」,而 Object 不行。
Q2:为什么有 <T> 还要用 <?>?
<?> 是通配符,用于未知类型;<T> 是类型参数,用于定义泛型:
java
// <T> 定义一个泛型,用在方法/类上
public <T> void print(T item) { } // T 是方法自己定义的
// <?> 用于接收各种类型
public void printAll(List<?> list) { } // 不知道什么类型,但能用 Object记住:<T> 是生产者,<?> 是消费者。
Q3:<? extends T> 和 <? super T> 区别?
? extends T → 「读」出来是 T,「写」不进去
? super T → 「写」进去 T,「读」出来是 Objectjava
// extends:生产者,只能读
public double sum(List<? extends Number> nums) {
for (Number n : nums) { // 能读
sum += n.doubleValue();
}
nums.add(1); // 不能写!不知道是什么子类
}
// super:消费者,只能写
public void addIntegers(List<? super Integer> list) {
list.add(1); // 能写,Integer 是任何 Integer 父类的子类
// list.get(0); // 能读但返回 Object
}记忆口诀:PECS — Producer Extends, Consumer Super。
Q4:泛型类静态成员不能用泛型类型?
java
public class Container<T> {
private static T instance; // 编译错误!
public static T getInstance() { // 编译错误!
return instance;
}
}因为静态成员属于类,而泛型 T 属于实例。如果允许,类和实例的关系就乱了。
解决方案:把泛型移到方法上:
java
public static <T> T getInstance(Class<T> clazz) {
return clazz.getDeclaredConstructor().newInstance();
}Q5:如何获取泛型的实际类型?
运行时泛型会被擦除,但可以通过反射间接获取:
java
public class Parent<String, Integer> {}
// 获取父类的泛型参数
public class Child extends Parent<String, Integer> {}
ParameterizedType type = (ParameterizedType)
Child.class.getGenericSuperclass();
Type[] args = type.getActualTypeArguments();
System.out.println(args[0]); // class java.lang.String
System.out.println(args[1]); // class java.lang.Integer或者用 匿名内部类:
java
// 创建时保留泛型信息
TypeToken<List<String>> token = new TypeToken<List<String>>(){};
Type type = token.getType(); // 包含完整泛型信息Q6:泛型能不能用于异常?
不能直接抛出泛型异常:
java
public <T extends Exception> void process(T ex) {
throw ex; // OK,可以抛
throw new T(); // 编译错误!不能 new T()
}原因:类型擦除后 new T() 不知道 new 什么。
替代方案:使用反射或异常链。
Q7:泛型擦除后为什么需要桥接方法?
为了保持多态一致:
java
// 源码
public class Node<T> {
public void set(T value) { }
}
public class StringNode extends Node<String> {
@Override
public void set(String value) { } // 覆盖
}编译后:
java
public class Node {
public void set(Object value) { } // 原始方法
}
public class StringNode extends Node {
public void set(String value) { } // 覆盖
// 编译器生成的桥接方法
public void set(Object value) {
this.set((String) value); // 桥接
}
}没有桥接方法,多态就乱了。
Q8:为什么泛型不能有泛型数组?
java
List<String>[] arr = new List<String>[10]; // 编译错误因为类型擦除后,List<String> 和 List<Integer> 在运行时都是 List,无法区分数组元素类型。
如果允许:
java
List<String>[] arr = new List<String>[10];
Object[] objArr = arr; // 假设允许
objArr[0] = new ArrayList<Integer>(); // 破坏类型安全解决方案:用 List<List<String>> 替代。
Q9:泛型方法和方法重载冲突?
java
public class Example {
// 这两个方法不能同时存在!
public void method(List<String> list) { }
public void method(List<Integer> list) { }
}因为擦除后都是 method(List list),冲突了。
解决方案:用不同的方法名,或者改变参数类型。
Q10:泛型和注解有什么关系?
注解可以标注泛型类型,JDK 8+ 支持类型注解:
java
@NotNull List<String> names; // 标注类型
@NonNull Map<String, @Positive Integer> scores; // 多层标注总结
泛型的核心原则只有一个:类型安全,把错误提前到编译期。
记住这个原则,所有泛型规则都能推导出来。
