【关于Java的泛型(高级)】

发布于:2025-08-08 ⋅ 阅读:(16) ⋅ 点赞:(0)

上一篇我们聊了泛型的基础——怎么用。
今天,我们来揭开泛型的底层原理。


一、泛型的“假象”——类型擦除(Type Erasure)

你有没有想过:为什么 Java 的泛型不能用于 new T() 或判断 if (obj instanceof T)

答案就藏在 Java 泛型的核心机制中:类型擦除(Type Erasure)

1. 什么是类型擦除?

简单说:泛型只存在于编译期,运行时会被“擦掉”

Java 的泛型是通过“类型擦除”实现的,也就是说:

  • ✅ 编译时:编译器用泛型做类型检查,确保类型安全。
  • ❌ 运行时:JVM 根本不知道泛型的存在,所有的泛型信息都被“擦除”了。

举个例子:

List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();

// 问:这两个 List 的运行时类型一样吗?
System.out.println(strList.getClass() == intList.getClass()); // 输出:true

你没看错!输出是 true

因为在运行时,List<String>List<Integer> 都变成了 List,泛型信息被擦除了。

2. 擦除后变成什么?

  • 如果没有限定类型(比如 <T>),擦除后变成 Object
  • 如果有限定类型(比如 <T extends Number>),擦除后变成上限类型(这里是 Number)。
public class Box<T extends Number> {
    private T value;
    public T getValue() { return value; }
}

编译后相当于:

public class Box {
    private Number value;  // T 被擦除为 Number
    public Number getValue() { return value; }
}

3. 为什么要类型擦除?

这是 Java 为了兼容老版本(Java 5 之前没有泛型)而做的设计。它让泛型代码可以和非泛型代码共存,但代价是:

  • 不能在运行时获取泛型类型
  • 不能 new T()
  • 不能 instanceof T

二、通配符的上下限:? extends T 和 ? super T

泛型中有一个非常重要的概念:通配符(Wildcard),尤其是它的上下限用法。

1. ? extends T —— 上限通配符(Upper Bounded Wildcard)

表示“某种类型,它是 T 或 T 的子类”。

List<? extends Number> list;

这个 list 可以是:

  • List<Number>
  • List<Integer>
  • List<Double>
  • ……

但你不能往里面添加元素(除了 null),因为编译器不知道具体是哪种子类型。

list.add(new Integer(1)); // ❌ 编译错误!
Number n = list.get(0);   // ✅ 可以读取,因为一定是 Number 或其子类

👉 适用场景:只读操作(Producer)

2. ? super T —— 下限通配符(Lower Bounded Wildcard)

表示“某种类型,它是 T 或 T 的父类”。

List<? super Integer> list;

这个 list 可以是:

  • List<Integer>
  • List<Number>
  • List<Object>

你可以往里面添加 Integer 或其子类:

list.add(new Integer(1)); // ✅ 可以添加
Object obj = list.get(0); // ❌ 只能拿到 Object,类型信息丢失

👉 适用场景:只写操作(Consumer)


三、PECS 原则:搞定上下限的“黄金口诀”

记不住什么时候用 extends,什么时候用 super?记住这个口诀:

PECS = Producer-Extends, Consumer-Super

  • 如果你从集合中读取数据(它是“生产者”),用 ? extends T
  • 如果你往集合中写入数据(它是“消费者”),用 ? super T

举个 JDK 中的经典例子:Collections.copy()

public static <T> void copy(List<? super T> dest, List<? extends T> src)
  • src 是“生产者” → 用 ? extends T(从里面读 T 类型的数据)
  • dest 是“消费者” → 用 ? super T(往里面写 T 类型的数据)

完美符合 PECS!


四、泛型的局限性(你知道吗?)

由于类型擦除,Java 泛型有一些“坑”:

限制 说明
❌ 不能创建泛型数组 new T[] 不合法,因为运行时不知道 T 是什么
❌ 不能用于基本类型 List<int> 不行,要用 List<Integer>
❌ 不能用于 instanceof obj instanceof List<String> 不合法
❌ 静态变量不能使用泛型类型 private static T t; 不合法,因为静态变量属于类,而泛型是实例相关的

五、面试高频问题(附参考回答)

Q1:Java 泛型是怎么实现的?为什么叫“伪泛型”?

A:Java 泛型是通过“类型擦除”实现的,泛型信息只在编译期存在,运行时会被擦除。因此它被称为“伪泛型”,与 C# 的“真泛型”不同。

Q2:List<Object> 和 List<String> 有继承关系吗?

A:没有!List<String> 不是 List<Object> 的子类。这是泛型的“不可变性”。如果需要兼容,要用通配符,比如 List<? extends Object>

Q3:? extends T 和 ? super T 有什么区别?什么时候用?

A:? extends T 表示 T 及其子类,适合“读取”;? super T 表示 T 及其父类,适合“写入”。遵循 PECS 原则:生产者用 extends,消费者用 super


六、总结:泛型的本质

概念 关键点
类型擦除 泛型只在编译期存在,运行时被擦除为 Object 或上限类型
通配符 ? 表示未知类型,extends 是上限,super 是下限
PECS 原则 生产者用 extends,消费者用 super
局限性 不能 new T、不能用基本类型、不能 instanceof 等

写在最后

Java 泛型看似简单,实则暗藏玄机。理解了类型擦除PECS 原则,你就掌握了泛型的“内功心法”。

下次面试官再问:“说说泛型的原理”,你就可以从容不迫地说:

“Java 泛型基于类型擦除实现,编译期检查类型安全,运行时擦除泛型信息。为了灵活处理类型兼容,我们使用 ? extends T? super T,并遵循 PECS 原则……”


网站公告

今日签到

点亮在社区的每一天
去签到