自Java 1.5开始使用的泛型,给人比较简单的印象是..."尖括号里写了类型我就不用检查类型也不用强转了"。
好的,那先从API的使用者的角度上想问题,泛型还有什么意义?
有句话叫:Discover errors as soon as possible after they are made, ideally at compile time.
泛型提供的正是这种能力。
比如有一个只允许加入String的集合,在没有声明类型参数的情况下,这种限制通常通过注视来保证。
接着在集合中add一个Integer实例,从集合获取元素时使用强转,结果导致ClassCastException。
这样的错误发生在运行时,compiler就爱莫能助了,而声明了类型参数的情况下则是compile-time error。
相比raw type,泛型的优势显而易见,即安全性和表述性。
有了泛型就一定比raw type强吗?
如果类型参数是Object又如何? 这种用法和raw type有什么区别?
如果仅仅通过代码描述,可以说:raw type不一定支持哪个类型,而Collection<Object>支持任何类型。
正确,但没什么意义。
泛型有种规则叫subtyping rules,比如List<String>是List的子类型,但不是List<Object>的子类型。
下面的代码描述了这种情况:
// Uses raw type (List) - fails at runtime! public static void main(String[] args) { List<String> strings = new ArrayList<String>(); unsafeAdd(strings, new Integer(42)); String s = strings.get(0); // Compiler-generated cast } private static void unsafeAdd(List list, Object o) { list.add(o); }
上面的情况导致运行时才能发现错误,而下面这种做法则是编译无法通过:
Test.java:5: unsafeAdd(List<Object>,Object) cannot be applied to (List<String>,Integer) unsafeAdd(strings, new Integer(42)); ^
而为了应对这种情况Java提供了unbounded wildcard type。
即,对于不确定的类型参数使用‘?’代替。
比如Set<?>可以理解为某个类型的集合。
编码时几乎不会用到raw type,但也有两个例外,而且都和泛型擦除有关。
1. must use raw types in class literals.
2.it is illegal to use the instanceof operator on parameterized type.
既然如此,为什么Java还保留着raw type用法?
Java 1.5发布的时候Java马上要迎来第一个十年,早已存在大量的代码。
老代码和使用的新特性的代码能够互用至关重要,即 migration compatibility 。
好了,接下来说说使用泛型方面的事情。
首先是subtyping,总觉得因为这一特征,泛型有时反而显得麻烦。
书中原话是convariant和invaritant。
比如,数组是convariant的,比如Sub是Super的子类,则Sub[]是Super[]的子类。
反之,泛型是invariant的,List<Sub>不是List<Super>的子类。
鉴于这种区别,数组和泛型的难以混合使用。
比如下面这几种写法都是非法的:
new List<E>[]
new List<String>[]
new E[]
下面通过一段代码说明泛型数组为什么非法:
// Why generic array creation is illegal - won‘t compile! List<String>[] stringLists = new List<String>[1]; // (1) List<Integer> intList = Arrays.asList(42); // (2) Object[] objects = stringLists; // (3) objects[0] = intList; // (4) String s = stringLists[0].get(0); // (5)
Effective Item 14 - 关于泛型