Skip to content

泛型常见问题

泛型十问

泛型在使用中有很多容易混淆的地方,这里整理了最常见的 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,「读」出来是 Object
java
// 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; // 多层标注

总结

泛型的核心原则只有一个:类型安全,把错误提前到编译期

记住这个原则,所有泛型规则都能推导出来。

基于 VitePress 构建