类型安全的异构容器

当在 JDK5 中为 Java 添加泛型时,Neal Gafter 将类 java.lang.Class 更改为泛型类型。例如,String.class 的类型现在变为 Class<String>Gilad Bracha 为此创造了术语 type token - 类型令牌。作者的意图是启用特定样式的 API,Joshua Bloch 称之为 THC,或 Typesafe Heterogenous Container - 类型安全的异构容器模式,在 Effective Java 第 33 项中详细描述了该模式。

THC

有关使用类型令牌的一些示例,请参阅注解的 API:

1
public <A extends Annotation> A java.lang.Class.getAnnotation(Class<A> annotationClass)

下面是 Effective Java 中一个简单但完整的 API 示例,该示例使用了类型令牌实现了 THC 模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Favorites {
private final Map<Class<?>, Object> favorites = new HashMap<>();

public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), type.cast(instance));
}

public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}

public static void main(String[] args) {
Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebabe);
f.putFavorite(Class.class, Favorites.class);

String favoriteString = f.getFavorite(String.class);
int favoriteInteger = f.getFavorite(Integer.class);
Class<?> favoriteClass = f.getFavorite(Class.class);
}
}

getFavorite 首先从 favorites Map 中获取与给定 Class 对象相对应的值。可以返回的正确对象引用,但它具有错误的编译时类型:它是 Object(favorites map 的值类型),我们需要返回类型 T。因此,getFavorite 实现使用 Class 的 cast 方法动态地将对象引用转换为 Class 对象表示的类型。
cast 方法是 Java 的 cast 操作符的动态模拟。它只是检查它的参数是否由 Class 对象表示的类型的实例。如果是,它返回参数;否则会抛出 ClassCastException 异常。我们知道,假设客户端代码能够干净地编译,getFavorite 中的强制转换不会抛出 ClassCastException 异常。也就是说,favorites map 中的值始终与其键的类型相匹配。
那么这个 cast 方法为我们做了什么,因为它只是返回它的参数?cast 的签名充分利用了 Class 类是泛型的事实。它的返回类型是 Class 对象的类型参数:

1
2
3
public class Class<T> {
T cast(Object obj);
}

这正是 getFavorite 方法所需要的。这正是确保 Favorites 类型安全,而不用求助一个未经检查的强制转换的 T 类型。

Favorites 对象充当类型安全的 map 映射类型令牌到该类型的实例。这种模式的有趣之处在于,单个 Favorites 对象可用于保存多种(即异构)类型的事物,但是以类型安全的方式。当你想得到其中最喜欢的 String 时,它是 String 类型,而不必强制转换它,所以称之为类型安全的异构容器。

这种模式有一个限制。类型擦除将导致下面的代码不可编译:

1
2
3
Favorites:15: illegal start of expression
f.putFavorite(List<String>.class, Collections.emptyList());
^

因为 Java 会在编译期间擦除范型类型信息。因此,泛型类型参数只是源代码的产物,在运行时将不存在。

如果我们需要支持范型类型或可具体化类型,我们需要一种该模式的扩展,超级类型令牌 - Super Type Tokens。

可具体化类型

由于在编译期间会擦除某些类型信息,因此在运行时并非所有类型都可用。在运行时完全可用的类型称为可具体化的类型 - Reifiable Types

当且仅当以下条件之一成立时,类型才是可具体化的:

  • 它引用的是非泛型类或接口类型声明。

  • 它是一种参数化类型,其中所有类型参数都是无界通配符,例如 List<?>HashMap<?, ?>

  • 它是基本类型,例如 long

  • 它是原始类型,例如 ListHashMap

  • 它是一种数组类型,其元素类型是可具体化的,例如 String[]int[]List[]Map<?, ?>[]

  • 它是一个嵌套类型,其中对于每个由“.”分隔的类型 T,T 本身是可具体化的。

    例如,如果泛型类 X<T> 具有泛型成员类 Y<U>,则类型 X<?>.Y<?> 是可具体化的,因为 X<?> 是可具体化的,而 Y<?> 是可具体化的。类型 X<?>.Y<Object> 不可具体化,因为 Y<Object> 不可具体化。

Super Type Tokens

Neal Gafter 想出了一种方法,它称之为:Super Type Tokens,该方法利用 Java 中匿名内部类的强大功能在编译时保留类型信息,它的简化形式如下:

1
public abstract class TypeReference<T> {}

抽象限定符是有意的。 它强制客户端将其子类化以创建 TypeReference 的新实例。 为 List<String> 制作一个超类型令牌,如下所示:

1
TypeReference<List<String>> x = new TypeReference<List<String>>() {};

事实证明,您可以使用超类型令牌来完成可以使用类型令牌完成的几乎所有事情,以及大多数您本来可以使用类型标记但需要支持泛型类型和可具体化类型的问题,等等。

上面创建的对象是一个匿名类,通过反射可以得到它的接口类型,包括泛型类型参数。下面是 Bob Lee 使用该模式的一个完整实例:

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
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
* References a generic type.
*
* @author crazybob@google.com (Bob Lee)
*/
public abstract class TypeReference<T> {

private final Type type;
private volatile Constructor<?> constructor;

protected TypeReference() {
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
}

/**
* 使用默认,无参构造实例化{@code T}实例
*/
@SuppressWarnings("unchecked")
public T newInstance()
throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException {
if (constructor == null) {
Class<?> rawType = type instanceof Class<?>
? (Class<?>) type
: (Class<?>) ((ParameterizedType) type).getRawType();
constructor = rawType.getConstructor();
}
return (T) constructor.newInstance();
}

/**
* 获取引用类型
*/
public Type getType() {
return this.type;
}

public static void main(String[] args) throws Exception {
List<String> l1 = new TypeReference<ArrayList<String>>() {}.newInstance();
List l2 = new TypeReference<ArrayList>() {}.newInstance();
}
}

Super Type Tokens 模式也被引用在多个框架中,例如:

参考链接