Open fxleyu opened 6 years ago
术语 | 示例 | 所在条目 |
---|---|---|
参数化的类型 | List<String> |
第 23 条 |
实际类型参数 | String |
第 23 条 |
泛型 | List<E> |
第 23、26 条 |
形式类型参数 | E |
第 23 条 |
无限制通配符类型 | List<?> |
第 23 条 |
原生态类型 | List |
第 23 条 |
有限制类型参数 | <E extends Number> |
第 26 条 |
递归类型参数 | <T extends Comparable<T>> |
第 27 条 |
有限制通配符类型参数 | List<? extends Number> |
第 28 条 |
泛型方法 | static <E> List<E> asList(E[] a) |
第 27 条 |
类型令牌 | String.class |
第 29 条 |
List
接口就只有单个类型参数 E
,表示列表的元素类型。从技术角度来看,这个接口的名称应该是指现在的 List<E>
(读作"E 的列表")。但是人们经常把它简称为 List
。泛型类和接口统称为 泛型(generic type)。<>
)把对应于泛型形式类型参数的实际类型参数列表括起来。例如,List<String>
(读作“字符串列表”)是一个参数化的类型,表示元素类型为 String
的列表。(String
是与形式类型参数 E
相对的实际类型参数。)List<E>
相对应的原生态类型是 List
。原生态类型就像从类型声明中删除所有泛型形象一样。实际上,原生态类型 List
与 Java 平台没有泛型之前的接口类型 List
完全一样。如上所述,如果不提供类型参数,使用集合类型和其他泛型也仍然是合法的,但是不应该这么做。如果使用原生类型,就失掉了泛型在安全性和表述性方面的所有优势。 移植兼容性(Migration Compatibility) 促成了支持原生态类型的决定。
原生态类型 List
和参数化的类型 List<Object>
之间到底有什么区别呢?不严格地说,前者逃避了泛型检查,后者则明确声明告知编译期,它能够持有任意类型的对象。
虽然你可以将 List<String>
传递给类型 List
的参数,但是不能将它传给类型 List<Object>
的参数。泛型有子类型(subtyping)的规则, List<String>
是原生态类型 List
的一个子类型,而不是参数化类型 List<Object>
的子类型。
如果使用像 List
这样的原生态类型,就会失掉类型安全性,但是如果使用像 List<Object>
这样的参数化类型,则不会。
List rawTypeList = Lists.newArrayList();
List<Object> genericList = Lists.newArrayList();
List<String> stringList = Lists.newArrayList();
rawTypeList = stringList;
genericList = stringList; // ERROR
ERROR 信息如下:
Incompatible types.
Required: List <java.lang.Object>
Found: List <java.lang.String>
Set<E>
的无限制通配符类型为 Set<?>
(读作“某个类型的集合”)。通配符类型是安全的,原生态类型则不安全。由于可以将任何元素放进使用原生态类型的集合中,因此很容易破坏该集合的类型约束条件;但 不能将任何元素(除了 null
之外)放到 Collection<?>
中。
你不仅无法将任何元素(除 null
之外)放进 Collection<?>
中,而且根本无法猜测你会得到哪种类型的对象。要是无法接受这些限制,就可以使用 泛型方法(generic method) 或者 有限制的通配符类型(bounded wildcard type)。
不要再新代码中使用原生态类型,这条规则有两个小小的例外,两者都源于“ 泛型信息可以在运行时被擦除 ”这一事实。
List.class
, String.class
和 int.class
都合法,但是 List<String>.class
和 List<?>.class
则不合法。instanceof
操作符是非法的。
if (o instanceof List) {
List<?> list = (List<?>) o;
}
使用原生态类型会在运行时导致异常,因此不要在新代码中使用。原生态类型只是为了引入泛型之前的遗留代码进行兼容和互用而提供的。
让我们做个快速的回顾:Set<Object>
是个参数化类型,表示可以包含任何对象类型的一个集合;Set<?>
则是一个通配符类型,表示只能包含某种未知对象类型的一个集合;Set
则是个原生态类型,它脱离了泛型系统。前两种是安全的,最后一种不安全。
用泛型编程时,会遇到很多编译器警告:非受检强制转化警告(unchecked cast warnings)、非受检方法调用警告、非受检普通数据创建警告、以及非受检转换警告(unchecked conversion warnings)。
要尽可能地消除每一个非受检警告。 如果消除了所有警告,就可以确保代码是类型安全的,这是一件很好的事情。这意味着不会在运行时出现 ClassCastException
异常,你会更加自信自己的代码可以实现预期的功能。
如果无法消除警告,同时可以证明引起警告的代码是类型安全的,(只有在这种情况下才)可以使用一个 @SuppressWarnings("unchecked")
注解来禁止这条警告。 如果忽略(而不是禁止)明知道是安全的非受检警告,那么当新出现一条真正有问题的警告时,你也不会注意到。新出现的警告就会淹没在所有的错误警告当中。
SuppressWarnings
注解可以用在任何粒度的级别中,从单独的局部变量声明到整个类都可以。应该始终在尽可能小的范围中使用 SuppressWarnings
注解。 永远不要在整个类上使用 SuppressWarnings
,这样做可能会掩盖了重要的警告。
如果你发现自己在长度不知一行的方法或者构造器中使用 SuppressWarnings
注解,可以将它移到一个局部变量的声明中。虽然你必须声明一个新的局部变量,不过这么做还是值得的。
每当使用 @SuppressWarnings("unchecked")
注解时,都要添加一条注解,说明为什么这么做是安全的。
非受检警告很重要,不要忽略它们。每一条警告都表示可能在运行时抛出 ClassCastException
异常。要尽最大努力消除这些警告。如果无法消除非受检异常,同时可以证明引起警告的代码是类型安全的,就可以在尽可能小的范围中,用 @SuppressWarnings("unchecked")
注解禁止该警告。要用注解把禁止该警告的原因记录下来。
数组与泛型相比,有两个重要的不同点。
首先,数组是协变的(covariant);相反,泛型则是不可变的(invariant)。协变只是表示如果 Sub
是 Super
的子类型,那么数组类型 Sub[]
就是 Super[]
的子类型。对于任意两个不同的类型 TypeA
和 TypeB
,List<TypeA>
既不是 List<TypeB>
的子类型,也不是 List<TypeB>
的超类型。你可能认为,这意味着泛型是有缺陷的,但实际上可以说数组才是有缺陷的。
@Test(expected = ArrayStoreException.class)
public void testArrayCovariant() {
Object[] objects = new Long[3];
objects[0] = "I don't fit in";
}
@Test
public void testListInvariant() {
// List<Object> objectList = new ArrayList<Long>(); // compile ERROR
}
String
保存到 Long
数组中,就会得到一个 ArrayStoreException
异常。相比之下,泛型只在编译时强化它们的类型信息,并在运行时丢弃(或者擦除)它们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码随意进行互用。由于上述这些根本的区别,因此数组和泛型不能很好地混合使用。
总而言之,数组和泛型有着非常不同的类型规则。数组是协变且可以具体化的;泛型是不可变的且可以被擦除的。因此,数组提供了运行时的类型安全,但是没有编译时的类型安全。反之,对于泛型也是一样。一般来说,数组和泛型不能很好地混合使用。如果你发现自己将它们混合起来使用,并且得到了编译时错误或者警告,你的第一反应就应该是用列表代替
Java 1.5 发行版本中增加了 泛型(Generic)。在没有泛型之前,从集合中读取到的每一个对象都必须进行转换。如果有人不小心插入了类型错误的对象,在运行时的转换处理就会出错。有了泛型之后,可以告诉编译器每个集合中接受哪些对象类型。编译器自动地为你的插入进行转化,并在编译期告知是否插入了类型错误的对象。这样可以使程序既更加安全,也更加清楚,但是要享有这些优势有一定的难度。
本章就是教你如何最大限度地享有这些优势,又能使整个过程尽可能地简单化。
本章目录