这是一篇初学者对 Java 泛型的理解,如果不对欢迎指出。
Java 中的泛型,在编译时期提供类型检查。在运行时期,为了让泛型零开销,泛型都被擦除。擦除的方式:
- 用 bound 替换泛型参数,如果泛型参数是 unbound 的,直接用 Object 类型替换。所以生成的字节码中,只包含原始的类,接口,和方法;
- 在必要的情况下生成类型的强制转换,来做到类型安全;
- Generate bridge methods to preserve polymorphism in extended generic types. (这句没看懂)
这种类型擦除的方式保证了没有新的 class 生成,使用泛型不会增加程序的开销。
在读了很多资料之后,我发现自己对泛型的误解主要是基于从 Python 来的印象。比如在 Animals 的一个 list 中,里面即可以有 Dog 又可以有 Cat,所以就以为在 Java 中 ArrayList<? extends Animal> 即可以有 Dog 又可以有 Cat,认为这样声明即表示这个 ArrayList 可以放任何 Animal 的子类。其实不是的,<? extends Animal> 是一个类型声明,最终只会表示一种类型。即这个 ArrayList 即可以是一个 Dog 的 ArrayList,也可以是一个 Cat 的 ArrayList,但不能有 Cat 又有 Dog。
基于此,Bound 其实是比较好理解的。
Upper Bound
顾名思义,就是类型的上界被确定了。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Dog extends Animal {}; class Cat extends Animal {}; class PersianCat extends Cat {}; public class Main { public void upperBound() { ArrayList<Dog> dogs = new ArrayList<>(); ArrayList<? extends Animal> dogAnimals = dogs; Animal animal = dogAnimals.get(0); System.out.println(animal); } } |
在这个例子中,上界是 Animal,所以 ArrayList<? extends Animal> 这个 ArrayList 就表示一种 Animal 的 List。可能是 Cat,也可能是 Dog。 ArrayList<? extends Animal> 是 ArrayList<Dog> 的子类,所以这个赋值可以成功。
对于写,因为 ArrayList 里面可以是任何 Animal 的子类,所以无法写入。不能 .add(new Dog()) 也不能 .add(new Animal()).
对于读,读出来的都是 bound 的类型,即 Animal。
Lower Bound
确定的是类型的下界,即允许这个类型的任何父类。
|
1 2 3 4 5 6 7 8 9 |
public void lowerbound() { ArrayList<Animal> animals = new ArrayList<>(); ArrayList<? super Cat> catSuper = animals; catSuper.add(new Cat()); catSuper.add(new PersianCat()); Object object = catSuper.get(0); System.out.println(object); } |
下界是 Cat,即这个 ArrayList 允许任何 Cat 的父类。所以这个 ArrayList 可以是 Animal 的 ArrayList,也可以是 Cat 的,也可以是 Object 的。
对于写,是允许的,因为下界是 Cat 了,那么任何 Cat 或者 Cat 的子类,都可以被转换成 <? super Cat>,所以允许写入 Cat 或 Cat 的子类。
对于读,因为不知道 ArrayList 的类型可能是什么,所以读出来的都是 Object,即最上面的 Bound。
这段代码实际被编译器抹去泛型的字节码反编译如下:
|
1 2 3 4 5 6 7 |
public void lowerbound() { ArrayList var1 = new ArrayList(); var1.add(new Cat()); var1.add(new PersianCat()); Object var3 = var1.get(0); System.out.println(var3); } |
本质上,Java 的泛型是通过编译器来实现的,编译器将我们写的有泛型的代码转换成没有泛型的代码。但是这个转换只是推导,增加类型转换,而不会生成新的类,也不会生成新的代码。在运行时不会有泛型的信息,没有额外的开销。
泛型是类型未确定,实际运行的时候,还是会确定到某一种类型上,时刻记住这是一门静态的语言。是否允许写入,读出来是什么类型这些问题,基于“保证”类型安全这个角度理解,就比较简单了。
更多阅读:
- Generics in the Java Programming Language
- Can’t add a ModuleInfo object to ArrayList<? extends ModuleInfo>
- Java generics type erasure: when and what happens?
感谢 messense 发我的资料和耐心解答。以上是个人理解,如果有误那肯定是我理解不到位。理解没有问题的话就是老师教得好。
https://stackoverflow.com/questions/8481301/covariance-invariance-and-contravariance-explained-in-plain-english/8482091#8482091