Java1.5 发行版增加了泛型(Generic)。
泛型出现前,集合读取的每个对象都必须进行转换,如果不小心插入类型错误对的对象,运行时的转换处理会报错。
泛型出现后,我们通过泛型可以告诉编译器每个集合可以接受哪些对象类型,让编译器自动为集合的元素插入进行转化,并且在编译时告知我们是否插入了类型错误的对象。
泛型类或泛型接口:声明中具有一个或多个类型参数(type parameter)的类或者接口,统称为泛型。eg,jdk1.5之后,List 接口只有单个类型参数E,表示列表的元素类型,所以他的接口名称应该是List<E>,但是人们常常把它简称为List。
参数化的类型(parameterized type),构成格式是:类或接口的名称 + 尖括号(<>)将泛型形式参数的实际类型参数列表括起来。
每个泛型都定义类一个 原生态类型(raw type),即不带任何实际类型参数的泛型名称。eg,List<E> 对应的原生态类型是List。原生态类型就相当于从类型声明中删除了泛型信息。
使用泛型进行编码,有两个好处:
-
优点1:让编写代码时在编译期及早发现错误,并且助于定位报错位置
-
优点2:集合使用泛型,从集合中遍历元素时不需要再进行手工转换了 (编译器替我们完成隐式转换,并确保过程不会失败,无论我们使用的是否for-each循环)
下面我们通过一个例子阐述清楚,代码如下:
private static void testGenericeBeforejdk5() { Collection stamps = new ArrayList(); stamps.add(new Coin()); for (Iterator i = stamps.iterator(); i.hasNext();){ Stamp next = (Stamp)i.next(); } } private static void testGenericeAfterjdk5() { Collection<Stamp> stamps = new ArrayList(); stamps.add(new Coin()); } static class Stamp{} static class Coin{}
-
testGenericeBeforejdk5()方法里,我们希望stamps集合只会存放Stamp 类元素,但是编码时还是不小心把一个coin放进了这个集合。那么程序是不会在编译时告诉程序员这个问题的,而是等到代码真正执行时,出现了异常。
Exception in thread "main" java.lang.ClassCastException: effectivejava.no23.TestGeneric$Coin cannot be cast to effectivejava.no23.TestGeneric$Stamp at effectivejava.no23.TestGeneric.testGenericeBeforejdk5(TestGeneric.java:26) at effectivejava.no23.TestGeneric.main(TestGeneric.java:14)
Error:(20, 28) java: 不兼容的类型: effectivejava.no23.TestGeneric.Coin无法转换为effectivejava.no23.TestGeneric.Stamp
其一、使用原生态类型,会失掉泛型在安全性和其他表述性方面的优势。
-
为什么继续允许使用原生态类型呢?Java 平台发展至今,已经存在大量的没有使用泛型的Java 代码了,人们认为让所有这些代码保持合法,且能够与泛型的代码互用,为了这个“移植兼容性”(Migration Compatibility)需求,促成了支持原生态类型的决定。
其二、原生态类型List 和 参数化类型List<Object>有区别。
-
原生态类型List,逃避了泛型检查,List<Object>则明确告知编译器:它能够持有任意类型的对象。eg,List<String>可以传递给List,但不能传递给 List<Object>。
-
泛型有子类型化(subtyping)的规则。List<String> 是原生态类型List 的一个子类型,但不是List<Object> 的子类型。
下面通过一个例子解读两者的区别:
private static void testSubTyping() { List<String> strings = new ArrayList<String>(); unsafeAdd(strings, new Integer(110)); String s = strings.get(0); } private static void unsafeAdd(List list, Object o) { list.add(0); } private static void unsafeAddV2(List<Object> list, Object o) { list.add(0); }
结论:使用List 这样的原生态类型会丢掉类型安全性,但是使用List<Object> 这样的参数化类型则不会。
不要在新代码中使用原生态类型,这条规则有两个小小的例外,原因是:泛型信息可以在运行时被编译器擦除了。
ClassLiteral:
TypeName {[ ]} . class
NumericType {[ ]} . class
boolean {[ ]} . class
void . class
A class literal is an expression consisting of the name of a class, interface, array, or primitive type, or the pseudo-type void, followed by a '.' and the token class.
https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.8.2
-
instanceof 操作符对“无限制通配符”的参数化类型是无效非法的。,由于泛型信息在运行中被擦除了,这种情况下,尖括号(<>)和问号(?)显得多余了。
private static void testInstanceOfInvalidOnGeneric(Object o) { if (o instanceof Set){ Set<?> set = (Set<?>) o; } }
涉及到的术语表
| 术语 |
示例 |
所在条目 |
| 参数化的类型 |
List<String> |
23 |
| 实际类型参数 |
String |
23
|
泛型
|
List<E>
|
23
|
形式类型参数
|
E
|
23
|
无限制通配符类型参数
|
List<?>
|
23 |
原生态类型参数
|
List
|
23
|
有限制类型参数
|
List<E extends Number>
|
26
|
递归类型限制
|
List<T extends Comparable<T>>
|
27
|
有限制通配符类型参数
|
List<? extends Number>
|
28
|
泛型方法
|
static <E> List<E> asList(E[] a)
|
27
|
类型令牌
|
String.class
|
29
|
本文分享自微信公众号 - 后台技术汇(gh_bbd0c11cb61f)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。