Skip to main content

泛型

David LiuAbout 2 min

泛型

Generics

JDK 5 中引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。

编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如 ArrayList<Persion> persons = new ArrayList<Persion>() 这行代码就指明了该 ArrayList 对象只能传入 Persion 对象,如果传入其他类型的对象就会报错。

  • 泛型类
  • 泛型接口
  • 泛型方法

泛型擦除

Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。

编译器会在编译期间会动态地将泛型 T 擦除为 Object 或将 T extends xxx 擦除为其限定类型 xxx 。

因此,泛型本质上其实还是编译器的行为,为了保证引入泛型机制但不创建新的类型,减少虚拟机的运行开销,编译器通过擦除将泛型类转化为一般类。

eg. 由于泛型擦除原理带来的一些特性:

  1. 泛型容器在编译期间添加不同类型会报错,但是运行期间通过反射可以添加
  2. 由于泛型擦除机制,泛型导致的重载方法会报错

既然编译器要把泛型擦除,那为什么还要用泛型呢?用 Object 代替不行吗?

这个问题其实在变相考察泛型的作用:

  • 使用泛型可在编译期间进行类型检测。
  • 使用 Object 类型需要手动添加强制类型转换,降低代码可读性,提高出错概率。
  • 泛型可以使用自限定类型如 T extends Comparable

桥方法(Bridge Method)

用于继承泛型类时保证多态,桥方法为编译器自动生成。

限制

通配符

  • extends
  • super

  • 假设你有一个 List<Integer>,并且你尝试将其赋值给一个 List<? extends Number> 类型的变量。从类型的角度来看,这是允许的,因为 IntegerNumber 的子类型。
  • 现在,如果 List<? extends Number> 允许添加任何 Number 的子类型,例如 Double,那么你将能够将 Double 添加到原本只能包含 Integer 的列表中。这显然是类型不安全的。

因此,为了防止这种类型不匹配的情况发生,Java 在使用通配符上界时禁止往这样的列表中添加元素(除了 null,因为 null 可以代表任何类型)。