在泛型篇开始介绍之前,要先明确几个书中的术语
术语 | 中文含义 | 举例 | 所在条目 |
---|---|---|---|
Parameterized type | 参数化类型 | List<String> | 条目 26 |
Actual type parameter | 实际类型参数 | String | 条目 26 |
Generic type | 泛型类型 | List<E> | 条目 26 和 条目 29 |
Formal type parameter | 形式类型参数 | E | 条目 26 |
Unbounded wildcard type | 无限制通配符类型 | List<?> | 条目 26 |
Raw type | 原始类型 | List | 条目 26 |
Bounded type parameter | 限制类型参数 | <E extends Number> | 条目 29 |
Recursive type bound | 递归类型限制 | <T extends Comparable<T>> | 条目 30 |
Bounded wildcard type | 限制通配符类型 | List<? extends Number> | 条目 31 |
Generic method | 泛型方法 | static <E> List<E> asList(E[] a) | 条目 30 |
Type token | 类型令牌 | String.class | 条目 33 |
26、不要使用原始类型(如List)
每一种泛型类型都定义一个原生态类型,例如List<String>
对应的原生态类型就是List,他们的存在主要是为了与泛型出现之前的代码兼容。
有了泛型之后,类型声明中可以包含信息,而不是通过注释去提醒:
private final Collection<Stamp> stamps = ....
从这个声明中,编译器知道stamps 集合应该只包含Stamp 实例,错误的插入会生成一个编译时错误消息,提醒具体是哪里出错了:
Test.java:9: error: incompatible types: Coin cannot be converted to Stamp
c.add(new Coin());
^
当从集合中检索元素时,编译器会为你插入不可⻅的强制转换
如果使用诸如List 之类的原始类型,则会丢失类型安全性,但是如果使用参数化类型(例如List<String>)则不会
原始类型List和参数化类型List 之间有什么区别呢?
前者逃避了泛型检查,而后者明确地告诉编译器,它能够保存任何类型的对象。
可以将List<String> 传递给List 类型的参数,但不能将其传递给List<Object> 类型的参数。泛型有子类型化的规则,List<String> 是原始类型 List 的子类型,但不是参数化类型List<Object> 的子类型
为了更直观的说明,给出下面的代码:
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, Integer.valueOf(42));
String s = strings.get(0); // Has compiler-generated cast
}
private static void unsafeAdd(List list, Object o) {
list.add(o);
}
如果运行该程序,则当程序尝试调用strings.get(0)
的结果(一个Integer)转换为一个String 时,会得到ClassCastException 异常。
如果在unsafeAdd
的声明中的原始类型List 替换参数化类型List<Object>,则编译器直接就会给出报错信息:
在不确定或不在意集合中元素类型时,可能会用到原始类型。例如编写一个返回两个集合中重复元素个数的程序:
static int numElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o1 : s1)
if (s2.contains(o1))
result++;
return result;
}
这种方法使用原始类型,是危险的。安全替代方式是使用无限制通配符类型(unbounded wildcard types)。如果要使用泛型类型,但不知道或关心实际类型参数是什么,则可以使用问号来代替。例如,泛型类型 Set<E>
的无限制通配符类型是Set<?>
static int numElementsInCommon(Set<?> s1, Set<?> s2){...}
“不要使用原始类型”这条规则有几个特例情况:
1. 必须在类签名(class literals)中使用原始类型
例如List.class
,String[].class
和int.class
都是合法的,但List<String>.class
和List<?>.class
不合法
2. 因为泛型类型信息在运行时被擦除,所以在<?>
以外的参数化类型上使用instanceof
是非法的
下面是使用泛型类型的instanceof 运算的示例:
if (o instanceof Set) { // Raw type
Set<?> s = (Set<?>) o; // Wildcard type
...
}
一旦确定 o 对象是一个Set,则必须将其转换为通配符Set<?>
。这是一个强制转换,所以不会导致编译器警告。