Java 泛型

类型系统

java类型系统.png

基本语法

  • java 的泛型没有 template 关键字。
  • 类型形参叫作 type variable,可以在类/方法里当具体类型如 String 使用,类型实参叫作 type parameter。也有些场景下,String 是 type argument。在甲骨文文档中的描述如下:

Type Parameters: K - the type of keys maintained by this map V - the
type of mapped values

  • 是否需要使用 type witness 取决于 compiler 是否有 enough information 来 infer 编译结果 - 又见 type inference。
  • 泛型方法的 type variable 在 modifier(public static)和 return value 之间。
  • List 是 generic type。List 是 Parameterized type。

绑定类型(bounding type)

  • 明确要绑定 type variable 到某个类型,才可以在接下来的方法里调用那个类型的方法,如需要 compareTo 方法,就必须<T extends Comparable<T>>extends本身可以后接多个绑定类型,用&分隔。

类型擦除(type erasure)

  • 类型擦除的结果的是将 type variable 替换成绑定类型(bounding types)的 ordinary class(或者说使用 raw type 的 class)。
  • 有了类型擦除,所有的泛型表达式都是可以被翻译的。带有泛型的代码被编译器翻译后通常分为两个部分:1. ordinary class,带有 bounding type,2. casting code,带有类型转换的代码。
  • cast code 是为了保证类型安全而存在的。
  • 假设 B extends A,我们调用calc(new C())的时候,父类型的相关方法总是会被擦除到 bounding type(假定 C 的擦除类型是 Object),所以子类的方法也带有这个 bounding type 的方法实现calc(Object object),但为了保持多态,编译器又生成了一个calc(C c)(相当于做了一次重载),真正要使用多态,就必须产生一个 synthesized bridge method,执行calc(Object object) {calc((C)object);}。这个东西是为了保持多态(preserve polymorphism)。泛型集成对比点 1。
  • 为了兼容性(compatibility)考虑,A a = new A<String>() 之类的赋值(从泛型到擦除类型的赋值总是会成立的)总是会成立,但编译器总是会报 warning。猫插入狗列表中问题,只有在真实地 set 和 get 操作时才会发生。在 Jackson 中经常遇见如下问题:实际反序列化的生成 class 是 LinkedHashMap,但 Entity> = JsonUtil.toObject(str)等还是会赋值成功(此处 实际得到的是 A)。

泛型不能做什么

所有泛型的 bug 都与一个基本假设有关,在虚拟机里运行的类型实际上使用的是 raw type,不注意这一点就可能出错。parameterized type vs raw type

  • 不能使用基本类型实例化 type parameters。
  • 动态类型识别只能在 raw type 上工作,也就是 instanceof、Class ==、getClass() 这类操作都只能当做 raw type 工作。
  • 不能创建 parameterized type 的数组(Pair<Sring>[] a = new Pair<String>[1]是不可能的) - 但可以赋值到泛型数组,见下一条。
  • 类型参数可以和 vargargs 一起工作,如果使用@safevarargs 连警告都不会有。也就是说这样的语句是成立的Pair<String>[] pair = makePairs(pair1, pair2)(这一条破坏了第三条规则)。但泛型数组还是危险的,因为它是协变(covariant)的。泛型数组适合被写,但不适合被读。尽量避免使用它,否则会出现很奇怪的运行时错误
  • 不能初始化 type variables,最常见的非法写法是new T()(但 T t 是很常见的)。一个 workaround 是在使用 T 的地方都使用 Class,然后借反射来生成对应的对象。
  • 不能创建泛型数组(T[] a = new T1)。
  • 泛型类的 type variables 不能在它的 static 上下文里工作。但静态方法自己可以有 type variables。-各有各的泛型。
  • 不能抛出和捕获异常类。
  • 巧妙地使用泛型,可以破坏 checked exception 的限制
  • 因为每个泛型方法都有一个兜底的 raw type 方法兜底,如果兜底方法和父类的非泛型方法相冲突(clash),编译会报错。举例,永远不要写boolean equals(T object),因为编译器会擦除出 boolean equals(Object object),制造同签名的方法,连重载都算不上。

泛型能够做什么

  • type variable 可以拿来做目标参数,如T t = get()(T)

通配符(wildcard)

?是通配符。
? extends T 作为一个 type parameter 证明可以在此处读。Pair<? extends Employee>意味着它的子类 可以是 Pair 和 Pair
? super T 作为一个 type parameter 证明可以在此处写。Pair<? super Employee>意味着它的子类 可以是 Pair 和 Pair

LocalDateChronoLocalDate的子类(顺序就是这样,没有反过来),但 ChronoLocalDate 已然实现了 comparable<ChronoLocalDate>。这时候 LocalDate 的比较方法就应该声明成<T extends Comparable<? super T>>- 这是 comparable 的标准泛型方案。从 A 派生出 B,则 B 的 comparable 方法必须声明为可以支持 super 的类型,这样对 A 的 compare 才能同时兼容 A、B - 而不只是 B,Lists 的 removeIf 方法的谓词同理。(泛型集成对比点 2)

通配符的存在实际上是为了放松“泛型不能支持协变”,而需要让程序员灵活使用多种实际类型做的一个妥协。

举例,Java 8 中的ArrayList 有个 removeIf 的方法,它的参数是个 predicate,但这个 predicate 的实参可以是,比如 Employee,也可以是 Object(用上了 super)。

Pair<?> 是个没用窝囊(wimpy)的类型,它的 setFirst(? object) 方法甚至无法被使用(试想,setFirst 怎样确定它的设值是兼容某个类型的?,?实际上近于 ? extends)。 但如果有些场景只是从某类 Pair 内部读值,那么 Pair<?> 比 Pair 更加简洁易读。

通配符捕获

? 不是 type variable,是 wildcard variable。

? a = Pair.getFirst() 是不合法的。
引入一个 T 来捕获通配符,就可以执行这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void <T> swapHelper(Pair<T> pair) {
// 只要不返回这个 a,就不会有编译问题,在方法体内可以随意使用 a。用有值的实例来获取泛型变量。
T a = Pair.getFirst();
}

// 甲骨文的例子
public class WildcardFixed {

void foo(List<?> i) {
fooHelper(i);
}

// Helper method created so that the wildcard can be captured
// through type inference.
private <T> void fooHelper(List<T> l) {
l.set(0, l.get(0));
}

}

在这里,T 捕获了通配符。T 不知道 ? 的具体类型,但知道它是某个确定类型。

编译器只有在确定 T 可以捕获确定的通配符的时候才允许编译通过。例如 List> 多了一个间接层,一个 list 可能有不同的 pair,持有不同的具体类型,编译器不会允许 List> 产生捕获。

泛型与反射

泛型与 class

String.class 是 Class 的一个 object。

Class 的 type variable 实际上限制了它方法的种种返回值。

反射能够在擦除后知道些什么

可以知道的以下东西:

  • 一个方法或者类型有个 type parameter T。
  • T 有 super 或者 extends 的 bound。
  • T 有个 wildcard variable。

不可以知道的东西

  • 到底运行时绑定的 type parameter 是什么?

反射的类型 hiarachy

反射的基础类型是 Type 接口,它有五个子类:

  • Class 类,描述具体类型(而不是接口)。我们常见的类型如果本身不带有<>就属于这一类型。对 Java 1.5 以后的泛型,一个 Map.class 和 Map 的 class 在求等上完全相等,但唯有引入其他 Type 子类才能说明 String 和 Integer 的存在。
  • TypeVariable 接口,描述类型参数,如 T
  • WildcardType 接口,描述通配符,如 ?。它必然可以找到 up bound(最起码有 Object),不一定有 down bound。
  • ParameterizedType 接口,描述泛型类或接口类型,如 Comparable<? super T> - 奇怪,是 T 而不是 String。常见的 ParameterizedType 有 Collection 的 Class 实例。可以简单把 ParameterizedType 理解为 指向 Collection 类的特殊的 class,它携带的 actualParamters 就是String/Integer 的 class,而 WildcardType 的 bounds 就是 extends A 的那个 A 的class。它还有一个 getRawType(也就是 Map),和 ownerType(也就是 Map 之于 Entry)。
  • GenericArray 接口,描述泛型数组如 T[]。这可以被用在获取方法参数上。获取到具体参数以后,可以把它转化为 ParameterizedType,然后获取它携带的 actualParamters。

TypeLiteral

有一个可以捕获多重泛型的实参的方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

// 1,当我们拿到一个Class,用Class. getGenericInterfaces()方法得到Type[],也就是这个类实现接口的Type类型列表。
// 2,当我们拿到一个Class,用Class.getDeclaredFields()方法得到Field[],也就是类的属性列表,然后用Field. getGenericType()方法得到这个属性的Type类型。
// 3,当我们拿到一个Method,用Method. getGenericParameterTypes()方法获得Type[],也就是方法的参数类型列表。

/**
* This constructor must be invoked from an anonymous subclass
* as new TypeLiteral<. . .>(){}.
*/
public TypeLiteral()
{
//
Type parentType = getClass().getGenericSuperclass();
if (parentType instanceof ParameterizedType)
{
// 重要:这里得到的 Type 是真的 Class 实例,不过做类型判定不能用 instanceof,只能用 assignableFrom。因为 Class 的自身类型和指代类型是不一样的。当然 Type 也可能是 ParameterizedType 等任意子类型,它们也可以通过 getClass 理解自身的类型。
type = ((ParameterizedType) parentType).getActualTypeArguments()[0];
}
else
throw new UnsupportedOperationException(
"Construct as new TypeLiteral&lt;. . .&gt;(){}");
}

// 上述 api 的例子
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Class<? extends List> firstClazz = list.getClass();
Type genericSuperclass = firstClazz.getGenericSuperclass();
// java.util.AbstractList<E>
System.out.println(genericSuperclass);

ParameterizedType parameterizedSuperType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedSuperType.getActualTypeArguments();
// E
Type actualTypeArgument = actualTypeArguments[0];
System.out.println(actualTypeArgument);

// java.util.AbstractCollection<E>
genericSuperclass = firstClazz.getSuperclass().getGenericSuperclass();
System.out.println(genericSuperclass);

parameterizedSuperType = (ParameterizedType) genericSuperclass;
actualTypeArguments = parameterizedSuperType.getActualTypeArguments();
// E
actualTypeArgument = actualTypeArguments[0];
System.out.println(actualTypeArgument);
}

// 但这个方法有一个例外,如:

private static class StringList extends ArrayList<String> {
}

public static void main(String[] args) {
List<String> list = new StringList();
Class<? extends List> firstClazz = list.getClass();
Type genericSuperclass = firstClazz.getGenericSuperclass();
// java.util.ArrayList<java.lang.String>
System.out.println(genericSuperclass);

ParameterizedType parameterizedSuperType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedSuperType.getActualTypeArguments();
// class java.lang.String 类型不再是 typevariable
Type actualTypeArgument = actualTypeArguments[0];
System.out.println(actualTypeArgument);
}

// 这个例外下面有用。

运行时捕获类型参数的方法

方法一

1
2
3
4
5
6
7
8
9
10
11
12
public class GenericClass<T> {

private final Class<T> type;

public GenericClass(Class<T> type) {
this.type = type;
}

public Class<T> getMyType() {
return this.type;
}
}

方法二

typetools

方法三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class TypeReference<T> implements Comparable<TypeReference<T>>
{
protected final Type _type;

protected TypeReference()
{
Type superClass = getClass().getGenericSuperclass();
if (superClass instanceof Class<?>) { // sanity check, should never happen
throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
}
/* 22-Dec-2008, tatu: Not sure if this case is safe -- I suspect
* it is possible to make it fail?
* But let's deal with specific
* case when we know an actual use case, and thereby suitable
* workarounds for valid case(s) and/or error to throw
* on invalid one(s).
*/
_type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
}

这个方法来自于 JDK 5 的作者的博客《super-type-tokens》

  1. abstract class 保证了这个类型必须通过子类确定,这样 getGenericSuperclass 必定会得到一个 ParameterizedType 而不仅仅是一个 GenericType。
  2. implements Comparable> 并不是真的希望子类覆写一个比较方法,而是希望子类型不要实现成一个 raw type。

保证了这两天 _type 一定是一个 concret class。

各种泛型黑魔法

Optional 里通配符转成类型参数

可见@SuppressWarnings("unchecked") java 的基础库里到处都是。

1
2
3
4
5
6
7
8
9
10
/**
* Common instance for {@code empty()}.
*/
private static final Optional<?> EMPTY = new Optional<>();

public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}

捕获 WildCard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

public TestClass {

// 任何相关的问题都可以写一个成员类来直接通过反射来实验
private List<? extends Number> listNum;

@Test
@SuppressWarnings("unchecked")
void testGetWildCardType() throws NoSuchFieldException {

final Field fieldNum = TestClass.class.getDeclaredField("listNum");
// genericType几乎必然是 ParameterizedTypeImpl 的实例
final Type genericType = fieldNum.getGenericType();
final ParameterizedType parameterizedType = (ParameterizedType) genericType;
final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
// wildCard 隐藏在 actualTypeArguments 里,不隐藏在
if (actualTypeArgument instanceof WildcardType) {
final WildcardType wildcardType = (WildcardType) actualTypeArgument;
final Type[] lowerBounds = wildcardType.getLowerBounds();
// 所有的 bound 都是 class
log.info(Arrays.toString(lowerBounds));
final Type[] upperBounds = wildcardType.getUpperBounds();
log.info(Arrays.toString(upperBounds));
}
}

Assertions.assertTrue(true);
}
}

确认消费类型的两种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

/**
* 验证一个类型是不是另一个类型的参数化子类
* <p>
* A implements B<C>
* 则 isGenericSubClass(A.class, B.class, C.class) 为 true
* 注意,如果存在 D extends C,C extends F
* D 作为第三个参数返回 true,而 F 返回 false
*
* @param subClass 子类型
* @param genericSuperClass 超类型,可以是借口
* @param targetGenericActualParameter 类型实参
* @return 认定结果
*/
public static boolean isGenericSubClass(Class<?> subClass, Class<?> genericSuperClass, Class<?> targetGenericActualParameter) {

if (!genericSuperClass.isAssignableFrom(subClass)) {
return false;
}
final Type genericSuperclass = subClass.getGenericSuperclass();

if (genericSuperclass instanceof ParameterizedType) {
final ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
final Type rawType = parameterizedType.getRawType();
// 这里可以确认 Collection 是不是 Collection-而不是 List
final boolean rawTypeMatch = rawType == genericSuperClass;
final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
// 这两行很重要,基本上能够获取类型实参就靠它了,但我们也只能得到 class
for (Type type : actualTypeArguments) {
if (type instanceof Class) {
if (rawTypeMatch && ((Class<?>) type).isAssignableFrom(targetGenericActualParameter)) {
return true;
}
}
}
}


final Type[] genericInterfaceTypes = subClass.getGenericInterfaces();
for (Type genericInterfaceType : genericInterfaceTypes) {
if (genericInterfaceType instanceof ParameterizedType) {
final ParameterizedType parameterizedType = (ParameterizedType) genericInterfaceType;
final boolean rawTypeMatch = parameterizedType.getRawType() == genericSuperClass;
final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
// 这两行很重要,基本上能够获取类型实参就靠它了,但我们也只能得到 class
for (Type type : actualTypeArguments) {
if (type instanceof Class) {
if (rawTypeMatch && ((Class<?>) type).isAssignableFrom(targetGenericActualParameter)) {
return true;
}
}
}
}
}
return false;
}

/**
* isGenericSubClass 的 Spring 版本,只能验证第一个 TypeParameter,验证第二个参数必然返回 false
*
* @param subClass 子类型
* @param genericSuperClass 超类型,可以是借口
* @param targetGenericActualParameter 类型实参
* @return 认定结果
*/
public static boolean isParameterized(Class<?> subClass, Class<?> genericSuperClass, Class<?> targetGenericActualParameter) {
final ResolvableType generic = ResolvableType.forClass(subClass).as(genericSuperClass).getGeneric();
return generic.isAssignableFrom(ResolvableType.forClass(targetGenericActualParameter));
}

其他资料

angelikalanger 的 JavaGenericsFAQ

空泛型数组的拷贝方法

1
2
3
4
5
6
7
8
9
10
11
12
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
int size = size();
if (a.length < size)
return Arrays.copyOf(this.a, size,
(Class<? extends T[]>) a.getClass());
System.arraycopy(this.a, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
Author: magicliang
Link: http://magicliang.github.io/2020/02/23/%E6%B3%9B%E5%9E%8B%E6%8B%BE%E9%81%97/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.