泛型编程相关理论学习
Variance
Variance describes how subtyping relationships between more complex types (e.g., generics, collections, or function types.) relate to subtyping relationships between their component types. It determines whether a type system allows substitutability in specific contexts, especially in generic programming.
- Invariance: The most restrictive relationship.
如果 A 是 B 的子类,但Container<A>
和Container<B>
没有任何关系。 - Covariance: Preserves the subtype relationship.
如果 A 是 B 的子类,则Container<A>
是Container<B>
的子类。 - Contravariance: Reverses the subtype relationship.
如果 A 是 B 的子类,则Container<A>
是Container<B>
的父类。
其中,Convariance
和Contravariance
与 OOP 中的多态性(Polymorphism)概念有密切联系。
具体可参考StackOverFlow中的回答以及 Eric Lippert 的系列文章
Invariance
Java 中的泛型具有不变性,无论 A 与 B 是否有继承关系,Container<A>
和Container<B>
都不会存在继承关系。
- Arrays differ from generic type in two important ways. First arrays are covariant. Generics are invariant.
- Covariant simply means if
X
is subtype ofY
thenX[]
will also be sub type ofY[]
. Arrays are covariant As string is subtype of Object SoString[]
is subtype ofObject[]
- Invariant simply means irrespective of
X
being subtype ofY
or not ,List<X>
will not be subType ofList<Y>
.《Effective Java》 Joshua Bloch
要验证这一点,可以考虑以下代码。如果ArrayList<Integer>
是ArrayList<Number>
的子类,那么以下代码应该是合法的。但实际上,编译器会报错。
A.isAssignableFrom(B)
方法可以判断两个类之间是否存在继承关系,主要在 JDK 源码中使用
需要注意的是,由于类型擦除机制(Type Erasure),ArrayList<Integer>
和 ArrayList<Number>
在运行时
在运行时的类型都是 ArrayList
,所以在运行时无法区分它们。通过以下代码判断两者的继承关系,会得到错误的结论
Covariance
java 中的数组是协变的,考虑以下代码;String
是Object
的子类,所以String[]
是Object[]
的子类。
JDK 开发人员在当时为什么要把数组设计成协变的而泛型为不可变的?感兴趣可以阅读https://stackoverflow.com/questions/18666710/why-are-arrays-covariant-but-generics-are-invariant
Contravariance
逆变性是协变性的反向操作。逆变性是指泛型的子类可以替代泛型的父类。
Erausre
Releated Concepts
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》
Type Erasure in 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:
- Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded.The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
- Insert type casts if necessary to preserve type safety.
- Generate bridge methods to preserve polymorphism in extended generic types.
Java 通过类型擦除确保了泛型的向后兼容性,但也可能导致方法签名的冲突,破坏多态性。桥接方法是一种由编译器生成的中间方法,目的是桥接父类和子类的方法实现,以保证多态性。
Ref
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