类型系统
Class
最常见的 Type 实现类
代表原始类型(raw types)和基本类型(primitive types):
1 2 Class<?> clazz = String.class; Class<?> arrayClass = int [].class;
ParameterizedType(参数化类型)
表示带有泛型参数的类型
主要方法:
getActualTypeArguments(): 获取泛型参数的实际类型
getRawType(): 获取原始类型
getOwnerType(): 获取所属类型
1 2 3 4 5 6 Map<String, List<Integer>> map = new HashMap <>();Type type = map.getClass().getGenericSuperclass();if (type instanceof ParameterizedType) { Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments(); }
TypeVariable(类型变量)
表示泛型中的类型变量
例如泛型定义中的 T、K、V 等
1 2 3 4 5 6 7 8 public class Container <T> { private T value; public void showType () { TypeVariable<?>[] typeParameters = Container.class.getTypeParameters(); } }
GenericArrayType(泛型数组类型)
表示元素类型是参数化类型或类型变量的数组
例如:T[] 或 List[]
1 2 3 4 5 6 7 8 9 10 11 12 public class Example <T> { T[] array; List<String>[] lists; public void check () { Field arrayField = Example.class.getDeclaredField("array" ); Type arrayType = arrayField.getGenericType(); if (arrayType instanceof GenericArrayType) { Type componentType = ((GenericArrayType) arrayType).getGenericComponentType(); } } }
WildcardType(通配符类型)
表示通配符泛型,比如 ?、? extends Number、? super Integer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class WildcardExample { List<? extends Number > numbers; public void examine () { Field field = WildcardExample.class.getDeclaredField("numbers" ); Type type = field.getGenericType(); if (type instanceof ParameterizedType) { Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments(); WildcardType wildcard = (WildcardType) actualTypeArguments[0 ]; Type[] upperBounds = wildcard.getUpperBounds(); Type[] lowerBounds = wildcard.getLowerBounds(); } } }
主要使用场景:
反射获取泛型信息
泛型类型系统的实现
框架开发中的类型判断和处理
注解处理器的开发
序列化/反序列化框架
这些类型系统的实现让 Java 能够在运行时获取和处理泛型信息,尽管 Java 的泛型是通过类型擦除实现的,但通过这些接口我们仍然可以在运行时获取到泛型的类型信息。
基本语法
java 的泛型没有 template 关键字。
三种基本概念:
Type Parameter(类型形参):在泛型类、接口或方法声明时使用的标识符。
Type Variable(类型变量):类型参数在代码中的引用。
可以认为是类型参数的一个”实例”。在 class Box<T> { T value; }
中,字段类型T是一个类型变量。许多文档中”类型参数”和”类型变量”可互换使用。
Type Argument(类型实参):在使用泛型类型时,提供的具体类型。
在调用或实例化时提供。Box 中的 Integer。1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class List <T> { private T[] elements; public void add (T element) {...} public <E> void addAll (Collection<E> items, Function<E, T> converter) {...} } List<String> names = new ArrayList <>();
泛型方法的 type variable 在 modifier(public static
)和return value
之间。
List<T>
是 generic type。List<String>
是 Parameterized type。
绑定类型(bounding type) 在Java泛型的上下文中,”bound”一词最准确的翻译是“边界”(boundary) 而非”绑定”。Java官方文档一致使用”upper bound”(上边界)和”lower bound”(下边界)这样的术语,这些术语在数学和集合论中表示边界概念。
明确要绑定 type variable 到某个类型-得到 type argument,才可以在接下来的方法里调用那个类型的方法,如需要 compareTo
方法,就必须<T extends Comparable<T>>
。extends
本身可以后接多个绑定类型,用&
分隔。
递归泛型边界模式(Recursive Generic Type Bounds) 这是被称作”F-bounded多态”(F-bounded polymorphism)或”递归泛型边界”的高级泛型设计模式。
1 2 public class RepresentationModel <T extends RepresentationModel <? extends T >> { }
T必须是RepresentationModel的子类
并且这个子类的泛型参数必须是T自身或T的某个子类
RepresentationModel - type parameter 是 -> T <- 是子类,且这个子类的 type argument 是 T 自己 - RepresentationModel
。
如果不使用这种类型:
1 2 3 4 5 6 7 8 9 10 11 12 public class ResourceModel { public ResourceModel addLink (String rel, String href) { return this ; } }public class UserModel extends ResourceModel { private String username; }
那么使用时会遭遇编译错误:
1 2 3 4 5 UserModel user = new UserModel ();ResourceModel result = user.addLink("self" , "/users/123" ); String username = result.getUsername();
这个问题的核心是,有些 fluent API 本身返回自身,这种自身不能返回子类时,父子混合 build 会产生奇怪的问题。
解法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class RepresentationModel <T extends RepresentationModel <? extends T >> { public T addLink (String rel, String href) { return self(); } @SuppressWarnings("unchecked") private T self () { return (T) this ; } }public class UserModel extends RepresentationModel <UserModel> { private String username; public String getUsername () { return username; } }
使用时:
1 2 3 4 5 UserModel user = new UserModel ();UserModel result = user.addLink("self" , "/users/123" );String username = result.getUsername();
这种方案的做法是:
父类所有方法都使用 T 作为 type variable。在父转子的时候,使用转型,忽略警告。
流式调用的时候都转为自己。
核心优势:
类型安全的方法链:子类调用父类方法时返回的仍然是子类类型
代码复用:父类可以实现通用功能,子类无需重写这些方法
IDE支持:智能提示和自动完成可以正确显示返回对象的可用方法
这种模式广泛应用于各种Java库中:
Java的Enum<E extends Enum<E>>
。
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {}
确保类型安全:> 这种递归泛型定义确保了枚举类型只能被其子类实例化
提供类型正确的方法:使得像 compareTo 这样的方法能够正确接收子类类型的参数
实现自引用泛型:允许枚举类引用自身类型
JPA的CriteriaBuilder
Stream API-易被忽略
各种构建器模式实现
类型擦除(type erasure)
类型擦除的结果的是将 type variable 替换成绑定类型(bounding types)的 ordinary class(或者说使用 raw type 的 class)。
有了类型擦除,所有的泛型表达式都是可以被翻译的。带有泛型的代码被编译器翻译后通常分为两个部分:1. ordinary class,带有 bounding type,2. casting code,带有类型转换的代码。
cast code 是为了保证类型安全而存在的。
假设B extends A<C>
,我们调用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<A<B>> = JsonUtil.toObject(str)
等还是会赋值成功(此处 实际得到的是A<LinkedHashMap>
)。
Type Witness 类型见证/目击者 1 2 3 4 5 6 7 8 9 SomeClass.<TypeName>methodName(arguments)var result = Collections.<String>emptyList(); someMethod(Collections.<Map<String, Integer>>emptyList()); processStringList(Collections.emptyList());
其中第二种情况更常见,如果方法调用/=左边有足够的目标类型,编译器不会产生有歧义的推导(Inference)路径。
泛型不能做什么 所有泛型的 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>
,然后借反射来生成对应的对象。
不能创建泛型数组(T[] a = new T[1]
)。
泛型类的 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<Employee>
和Pair<Manager>
。 ? super T 作为一个 type parameter 证明可以在此处写。Pair<? super Employee>
意味着它的子类 可以是Pair<Employee>
和Pair<Object>
。
LocalDate
是ChronoLocalDate
的子类(顺序就是这样,没有反过来),但 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<T>
内部读值,那么Pair<?>
比Pair<T>
更加简洁易读。
通配符捕获 ? 不是 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) { T a = Pair.getFirst(); }public class WildcardFixed { void foo (List<?> i) { fooHelper(i); } private <T> void fooHelper (List<T> l) { l.set(0 , l.get(0 )); } }
在这里,T 捕获了通配符 。T 不知道 ? 的具体类型,但知道它是某个确定类型。
编译器只有在确定 T 可以捕获确定的通配符的时候才允许编译通过。例如List<Pair<?>>
多了一个间接层,一个 list 可能有不同的 pair,持有不同的具体类型,编译器不会允许 List<Pair<T>>
产生捕获。
另一个例子:
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 public TestClass { private List<? extends Number > listNum; @Test @SuppressWarnings("unchecked") void testGetWildCardType () throws NoSuchFieldException { final Field fieldNum = TestClass.class.getDeclaredField("listNum" ); final Type genericType = fieldNum.getGenericType(); final ParameterizedType parameterizedType = (ParameterizedType) genericType; final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { if (actualTypeArgument instanceof WildcardType) { final WildcardType wildcardType = (WildcardType) actualTypeArgument; final Type[] lowerBounds = wildcardType.getLowerBounds(); log.info(Arrays.toString(lowerBounds)); final Type[] upperBounds = wildcardType.getUpperBounds(); log.info(Arrays.toString(upperBounds)); } } Assertions.assertTrue(true ); } }
泛型与反射 泛型与 class String.class 是Class<String>
的一个 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<String, Integer>
的 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 public TypeLiteral () { Type parentType = getClass().getGenericSuperclass(); if (parentType instanceof ParameterizedType) { type = ((ParameterizedType) parentType).getActualTypeArguments()[0 ]; } else throw new UnsupportedOperationException ( "Construct as new TypeLiteral<. . .>(){}" ); } public static void main (String[] args) { List<String> list = new ArrayList <>(); Class<? extends List > firstClazz = list.getClass(); Type genericSuperclass = firstClazz.getGenericSuperclass(); System.out.println(genericSuperclass); ParameterizedType parameterizedSuperType = (ParameterizedType) genericSuperclass; Type[] actualTypeArguments = parameterizedSuperType.getActualTypeArguments(); Type actualTypeArgument = actualTypeArguments[0 ]; System.out.println(actualTypeArgument); genericSuperclass = firstClazz.getSuperclass().getGenericSuperclass(); System.out.println(genericSuperclass); parameterizedSuperType = (ParameterizedType) genericSuperclass; actualTypeArguments = parameterizedSuperType.getActualTypeArguments(); 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(); System.out.println(genericSuperclass); ParameterizedType parameterizedSuperType = (ParameterizedType) genericSuperclass; Type[] actualTypeArguments = parameterizedSuperType.getActualTypeArguments(); 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<?>) { throw new IllegalArgumentException ("Internal error: TypeReference constructed without actual type information" ); } _type = ((ParameterizedType) superClass).getActualTypeArguments()[0 ]; } }
这个方法来自于 JDK 5 的作者的博客《super-type-tokens》 。
abstract class 保证了这个类型必须通过子类确定,这样 getGenericSuperclass 必定会得到一个 ParameterizedType 而不仅仅是一个 GenericType。
implements Comparable<TypeReference<T>>
并不是真的希望子类覆写一个比较方法,而是希望子类型不要实现成一个 raw type。
保证了这两天 _type 一定是一个 concret class。
各种泛型黑魔法 Optional 里通配符转成类型参数 可见@SuppressWarnings("unchecked")
java 的基础库里到处都是。1 2 3 4 5 6 7 8 9 10 private static final Optional<?> EMPTY = new Optional <>(); public static <T> Optional<T> empty () { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; }
确认消费类型的两种方法 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 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(); final boolean rawTypeMatch = rawType == genericSuperClass; final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); 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(); for (Type type : actualTypeArguments) { if (type instanceof Class) { if (rawTypeMatch && ((Class<?>) type).isAssignableFrom(targetGenericActualParameter)) { return true ; } } } } } return false ; }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)); }
空泛型数组的拷贝方法 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; }
其他资料 angelikalanger 的 JavaGenericsFAQ 。