Generics and Subtyping
先看如下代码:
1 | package me.tianshuang; |
其中,第 10 行将造成编译错误。通常来说,如果 Foo
是 Bar
的子类型(子类或子接口),而 G
是某种泛型类型声明,则 G<Foo>
不是 G<Bar>
的子类型。这可能听起来违反直觉,可以这样理解,假设第二行编译成功且程序可以运行,那么我们可以往 lo
中放入任意类型的对象,而此时 lo
与 ls
指向的其实是同一个 List
的实例,那么我们可以从 ls
中获取到通过 lo
放入的对象,那么此时获取的对象的真实类型就不一定是 String
了,而是我们往 lo
中放入的对象的类型,比如以下代码:
1 | package me.tianshuang; |
其中第 12 行代码将尝试将一个 Object
类型的对象赋值给 String
类型的对象,所以 Java 编译器会阻止这种情况发生,在第二行时就会产生编译时错误,避免产生上述的问题。以上代码演示了泛型不是协变的,而在 Java 中数组是协变的,如果根据 《Effective Java中文版(第3版)》 泛型这一章中的说法,Java 中数组的设计是有缺陷的,比如以下代码:
1 | package me.tianshuang; |
编译并不会报错,但是在执行时会抛出运行时异常 ArrayStoreException
,异常信息如下:
1 | Exception in thread "main" java.lang.ArrayStoreException: java.lang.String |
利用数组,你会在运行时发现所犯的错误,利用列表,则可以在编译时发现错误。
Wildcards
Collection<?>
被称作未知类型的集合,在工具类 java.util.Collections 中可以看到对通配符的大量使用,如:Collections.shuffle(List<?>),什么时候应该使用通配符呢?可以看一个例子:
1 | package me.tianshuang; |
此时,第 10 行会编译出错,这是因为 Collection<String>
不是 Collection<Object>
的子类型,即 Collection<Object>
不是 Collection<String>
的超类型,那各种集合类型的超类型是什么呢?是 Collection<?>
,表示元素类型匹配任意东西的集合,将以上代码进行微调后:
1 | package me.tianshuang; |
即可通过编译,此时我们可以使用任意类型的集合来调用 printCollection
函数,注意在 printCollection
函数实现中,我们仍然可以从 c
中读取元素并赋予类型 Object
,这总是安全的,因为无论集合的实际类型是什么,集合都包含对象。但是,向其中添加任意对象是不安全的,如以下代码:
1 | package me.tianshuang; |
第 10 行将造成编译错误,因为我们不知道 c
集合中元素的类型代表什么,当实际的类型参数为 ?
时,它代表某种未知类型,我们传递给 add
方法的任何参数都必须是这种未知类型的子类型,因为我们不知道那是什么类型,所以我们不能传入任何对象,唯一的例外是 null
,它是所有类型的成员。
另一方面,给定一个 List<?>
,我们可以调用 get()
并使用结果。结果类型是未知类型,但我们始终知道它是一个对象。因此,将 get()
的结果分配给 Object
类型的变量或将其作为参数传递给需要类型 Object
的参数是安全的。
Class Literals as Runtime-Type Tokens
JDK 5.0 中的变化之一是 java.lang.Class
类是泛型的。这是一个将泛型用于容器类以外的东西的有趣示例。文档可以参见:Class
Type Parameters:
T - the type of the class modeled by this Class object. For example, the type of String.class is Class. Use Class<?> if the class being modeled is unknown.
T
代表 Class
对象所表示的类型,这可用于提高反射代码的类型安全性。
在 fastjson 的核心类 JSON 中,可以看到对 Class<T>
的使用:
1 | public static <T> T parseObject(String text, Class<T> clazz) { |
记录以上泛型相关的知识点是因为最近编写了基于 HTTP 请求的 SDK 库,在我的印象中,泛型大多数时候用于集合相关类,参考其他的请求库时发现不少实现将泛型用于对 API 请求的封装,类似于 Class<T>
的使用方式,且含有不少对 ?
的使用及对结果转换时 Class<T>
的使用,于是记录下来。
Reference
Generics and Subtyping (The Java™ Tutorials > Bonus > Generics)
Wildcards (The Java™ Tutorials > Bonus > Generics)
Class Literals as Runtime-Type Tokens (The Java™ Tutorials > Bonus > Generics)
Why are arrays covariant but generics are invariant? - Stack Overflow
AngelikaLanger.com - Java Generics FAQs - Frequently Asked Questions - Angelika Langer Training/Consulting
Generics and Overcoming Type Erasure on the JVM