我理解的 Java 泛型

前言

在学习 Spring 的依赖注入时, 被 Google 导流到了 Java Generics FAQs. 这篇文章深入讲解了 Java 中泛型相关的方方面面, 阅读完毕后, 整理了自己的一些理解.

概念与名词

在进入具体的讨论之前, 我们需要先明确几个名词的含义.

Generic Type

  • generic type && type parameter
    A generic type is a type with formal type parameters.
    interface List<E> {} 为例, List<E> 是 generic type, E 是 type parameter.

  • parameterized type && type argument
    A pameterized type is an instantation of a generic type with type argument.
    List<String> stringList; 为例, List<String> 是 parameterized type, 是 List<E> 的一个实例, String 是 type argument.

Wildcard

泛型中将通配符(wildcard)分为三类:

  • ? - unbound, 不做任何限定的通配符.
  • ? extends Number - upper bounded, 限定必须是 Number 或其子类.
  • ? super Integer - lower bounded, 限定必须是 Integer 或其父类.

后两者也被统称为 bounded wildcard.
结合通配符, parameterized type 也可以划分为三类.

                conceret type argument
generic type ---------------------------> conceret parameterized type

                unbound type argument
generic type ---------------------------> unbound parameterized type

                bounded type argument
generic type ---------------------------> bounded parameterized type
复制代码

Raw Type

Raw type 的存在是为了兼容引入泛型之前的版本, 大多数时候你都可以不用考虑它.

Type Erasure

严格说来, 泛型只存在于编译期间, JVM 并不感知泛型. 在编译时, 编译器通过 type erasure 来消除 type parameter 和 type argument.
具体的处理方式是:

  • Generic type 中的 type parameter 都会被其上确界(leftmost bound)所代替.
  • Parameterized type 中的 type argument 被直接移除, parameterized type 转变为对应的 raw type.

何为上确界, 对于 upper bounded type paramter 而言, 是指其公共父类, <? extends Number> 对应的就是 Number.
对于其他类型的 type paramter 而言, 因为 type argument 只能是引用类型(reference type), 而引用类型的公共父类是 Object, 所以其上确界都是 Object.

After type erasure
我们可以从 Java Generics FAQs 的例子中看到具体的转换过程. 左边是原始的代码, 右边是经过 type erasure 转换后的结果.

这个例子同时也反应了, 在 type erasure 的过程中, 编译器可能会按需加入 bridge methodtype cast.

泛型的类型系统

泛型的引入使得对象之间的继承关系变得更复杂, 如下这个例子中的一部分就是错误的.

public class SuperDemo {
    public static void main(String args[]) {
        List<Number> a = new ArrayList<Number>();
        ArrayList<Number> b = new ArrayList<Integer>();
        List<? extends Number> c = new ArrayList<Integer>();
        List<? super Number> d = new ArrayList<Object>();
        List<? super Integer> e = d;
    }
}
复制代码

理论上, 泛型相关的继承关系判断需要从两个纬度考虑:

  • generic type 之间是否有继承关系
  • type argument 之间是否有超集关系

具体而言. 对于 type argument 相同的情况, generic type 之间的继承关系决定两个 parameterized type 的父子关系, 所以 List<Number>ArrayList<Number> 的父类.
ArrayList<Number> 不是 ArrayList<Integer> 的父类, type argument 不同的情况下, 泛型之间的继承关系判断会很复杂. 主要是由于 wildcard 的存在, 导致 type argument 可以代表一类类型, 所以要引入集合中的超集(superset)概念, 即一方所代表的所有类型完全包含在以一方内.
最终的判断标准是, 在 type argument 不相同的情况下, 如果 type argument 是对方的超集, 而且 generic type 与对方相同或者是对方的父类, 那么当前的 parameterized type 才是对方的父类.

这时候再来回答以下的问题就会比较简单了:

泛型的特殊使用姿势

观察泛型在异常处理和数组中的使用限制, 思考是什么导致了这些限制, 在一定程度上可以验证自己之前的理解是否正确.

泛型与异常

Java 的异常处理在运行时生效, 而 type erasure 发生在编译期间, 所以大多数时候, 泛型在异常处理中并没有用武之地.

  • Java 不允许任何 Throwable 的子类是泛型. 这主要是由于 type erasure 导致 catch 不能在运行时区分一个 generic type 的不同实例, 所以把 error 或者 exception 定义为泛型不具有任何实际意义.
  • 同样由于 type erasure, catch 语句也不接受 type parameter.
  • throws 语句可以接受 type parameter, 编译器在编译时其会将 type parameter 的替换为具体的异常.
  • 你可以 throw 类型为 type parameter 的异常, 但实际上你基本没有机会这么做, 因为我们无法新建一个类型为 type parameter 的对象.

泛型与数组

与异常处理类似, 数组在运行时保存了每个元素的类型信息, 所以泛型数组也是一个没有太大意义的概念. 虽然可以定义一个数据的元素为泛型, 但我们仅能新建元素为 unbound parameterized type 的泛型数组. 具体而言, 下例子中 line 1 和 line 2 合法, 但 line 3 是错误的.

List<String>[] a;
List<?>[] b = new List<?>[10];
a = new List<String>[10]; // error
复制代码

究其根本, 是因为数据的组成元素都应该是同一类型的: An array is a container object that holds a fixed number of values of a single type. 而同一 generic type 对应的不同实例实质上并不等价, 但经过 type erasure 后, 并不能在运行时区分出这些.

假如能新建元素为 concerete parameterized type 的数组, 考虑如下案例.

List<String>[] stringLists = new List<String>[10];
stringLists[0] = new List<String>();
stringLists[1] = new List<Integer>();复制代码


猜你喜欢

转载自juejin.im/post/5c4134b66fb9a049b780a79c