虽然不应使用诸如List之类的原始类型,但可以使用参数化类型来允许插入任意对象(如List<Object>)。 原始类型List和参数化类型List<Object>之间有什么区别? 松散地说,前者已经选择了泛型类型系统,而后者明确地告诉编译器,它能够保存任何类型的对象。 虽然可以将List<String>传递给List类型的参数,但不能将其传递给List<Object>类型的参数。 泛型有子类型的规则,List<String>是原始类型List的子类型,但不是参数化类型List<Object>的子类型(条目 28)。 因此,如果使用诸如List之类的原始类型,则会丢失类型安全性,但是如果使用参数化类型(例如List <Object>)则不会。
为了具体说明,请考虑以下程序:
// Fails at runtime - unsafeAdd method uses a raw type (List)! 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); }此程序可以编译,它使用原始类型列表,但会收到警告:
Test.java:10: warning: [unchecked] unchecked call to add(E) as a member of the raw type List list.add(o); ^实际上,如果运行该程序,则当程序尝试调用strings.get(0)的结果(一个Integer)转换为一个String时,会得到ClassCastException异常。 这是一个编译器生成的强制转换,因此通常会保证成功,但在这种情况下,我们忽略了编译器警告并付出了代价。
如果用unsafeAdd声明中的参数化类型List <Object>替换原始类型List,并尝试重新编译该程序,则会发现它不再编译,而是发出错误消息:
Test.java:5: error: incompatible types: List<String> cannot be converted to List<Object> unsafeAdd(strings, Integer.valueOf(42));你可能会试图使用原始类型来处理元素类型未知且无关紧要的集合。 例如,假设你想编写一个方法,它需要两个集合并返回它们共同拥有的元素的数量。 如果是泛型新手,那么您可以这样写:
// Use of raw type for unknown element type - don't do this! 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 <?>(读取“某种类型的集合”)。 它是最通用的参数化的Set类型,能够保持任何集合。 下面是numElementsInCommon方法使用无限制通配符类型声明的情况:
// Uses unbounded wildcard type - typesafe and flexible static int numElementsInCommon(Set<?> s1, Set<?> s2) { ... }无限制通配符Set <?>与原始类型Set之间有什么区别? 问号真的给你放任何东西吗? 这不是要点,但通配符类型是安全的,原始类型不是。 你可以将任何元素放入具有原始类型的集合中,轻易破坏集合的类型不变性(如第119页上的unsafeAdd方法所示); 你不能把任何元素(除null之外)放入一个Collection <?>中。 试图这样做会产生一个像这样的编译时错误消息:
WildCard.java:13: error: incompatible types: String cannot be converted to CAP#1 c.add("verboten"); ^ where CAP#1 is a fresh type-variable: CAP#1 extends Object from capture of ?不可否认的是,这个错误信息留下了一些需要的东西,但是编译器已经完成了它的工作,不管它的元素类型是什么,都不会破坏集合的类型不变性。 你不仅可以将任何元素(除null以外)放入一个Collection <?>中,但是不能保证你所得到的对象的类型。 如果这些限制是不可接受的,可以使用泛型方法(条目 30)或有限制配符类型(条目 31)。
对于不应该使用原始类型的规则,有一些小例外。 你必须在类字面值(class literals)中使用原始类型。 规范中不允许使用参数化类型(尽管它允许数组类型和基本类型)[JLS,15.8.2]。 换句话说,List.class,String [] .class和int.class都是合法的,但List <String> .class和List <?>.class不是合法的。
规则的第二个例外涉及instanceof操作符。 因为泛型类型信息在运行时被删除,所以在无限制通配符类型以外的参数化类型上使用instanceof运算符是非法的。 使用无限制通配符类型代替原始类型不会以任何方式影响instanceof运算符的行为。 在这种情况下,尖括号和问号就显得多余。 以下是使用泛型类型的instanceof运算符的首选方法:
// Legitimate use of raw type - instanceof operator if (o instanceof Set) { // Raw type Set<?> s = (Set<?>) o; // Wildcard type ... }请注意,一旦确定o对象是一个Set,则必须将其转换为通配符Set <?>,而不是原始类型Set。 这是一个强制转换,所以不会导致编译器警告。