泛型设计成不变性有各方面的考量。但在理解起来可能有些别扭,苹果 IS A
水果,但装苹果的容器 IS NOT A
装水果的容器
为了更灵活且正确地使用泛型,可以考虑使用通配符(wildcard)。注意理解通配符的上下界限制,以及 PECS 原则。
Reifiable type
A reifiable type is a type whose type information is fully available at runtime. This includes primitives, non-generic types, raw types, and invocations of unbound wildcards.
examples | |
---|---|
Primitive types | int , double , boolean |
None-generic | String , Integer |
Raw types | List , Set |
Unbound wildcard | List<?> |
Non-reifiable type
examples | |
---|---|
parameterized types | List<String> , Map<Integer,String> |
wildcard types | List<?> , List<? extends Number> , List<? super Integer> |
Tips: reifiable = reify(vt. 使具体化)+ able
Heap Pollution
Heap pollution in Java occurs when a variable of a parameterized type refers to an object that is not of that type.
Homogeneous/Heterogeneous translations
Java 架构师 Brian Goetz 在Background: How We Got the Generics We Have 中解释了泛型的设计理念,以及为什么选择了类型擦除机制。在这篇文章中,他提到了 translating parameterised types 的两种方案
- Homogeneous translation: a generic class
Foo<T>
is translated into a single artifact, such asFoo.class
(and same for generic methods)- Heterogeneous translation: each instantiation of a generic type or method (
Foo<String>, Foo<Integer>
) is treated as a separate entity, and generates separate artifacts.Releated paper:《Two Ways to Bake Your Pizza— Translating Parameterised Types into Java》
Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:
public class Box<T> { private T value; public void set(T value) { this.value = value; } public T get() { return value; }}public class Foo<T extends Number> { private T value; public void set(T value) { this.value = value; } public T get() { return value; }}
public class Box { private Object value; public void set(Object value) { this.value = value; } public Object get() { return value; }}public class Foo { private Number value; public void set(Number value) { this.value = value; } public Number get() { return value; }}
Box<Integer> integerBox = new Box<>();integerBox.set(10);Integer value = integerBox.get();
Box integerBox = new Box();integerBox.set(10);Integer value = (Integer) integerBox.get(); // insert type cast
Java 通过类型擦除确保了泛型的向后兼容性,但也可能导致方法签名的冲突,破坏多态性。桥接方法是一种由编译器生成的中间方法,目的是桥接父类和子类的方法实现,以保证多态性。
class Parent<T> { public T getValue() { return null; }}// 泛型子类class Child extends Parent<String> { @Override public String getValue() { return "Child Value"; }}// Parent parent = new Child();// Object value = parent.getValue();// 调用链:// parent.getValue()// |// v// Child.bridge$getValue() // 桥接方法// |// v// Child.getValue() // 返回 String 类型的实际方法// |// v// "Child Value" (被转换为 Object 类型)public class Main { public static void main(String[] args) { Parent parent = new Child(); Object value = parent.getValue(); // JVM会调用桥接方法,再由桥接方法路由到子类方法 System.out.println(value); }}[OUTPUT] Child Value// 查看编译后的字节码$ javap -c Child[OUTPUT]class Child extends Parent<java.lang.String> { Child(); Code: 0: aload_0 1: invokespecial #1 // Method Parent."<init>":()V 4: return public java.lang.String getValue(); Code: 0: ldc #7 // String Child Value 2: areturn public java.lang.Object getValue(); Code: 0: aload_0 1: invokevirtual #9 // Method getValue:()Ljava/lang/String; 4: areturn}
docs.oracle.com/javase/tutorial/java/generics/erasure.html
docs.oracle.com/javase/tutorial/java/generics/nonReifiableVarargsType.html
An Expert‘s Guide to Generic Types in Java: Covariance, Contravariance, and Beyond
Java Generics - Bridge method? —— StackOverflow
Java bridge methods explained —— STAS
泛型编程相关理论学习