第5章.泛型
23.请不要在新代码中使用原生态类型
24.消除非受检警告
25.列表优先于数组
数组与泛型相比,有两个重要的不同点。首先,数组是协变的covariant,表示如果Sub为Super的子类型,那么数组类型Sub[]就是Super[]的子类型。相反,泛型是不可变的invariant,对于任意两个不同的类型Type1和Type2,List<Type1>
既不是List<Type2>
的子类型,也不是List<Type2>
的超类型。
Object[] objArr = new Long[1];
objArr[0] = "fails at runtime"; // throws ArrayStoreException
List<Object> ol = new ArrayList<Long>(); // incompatible types,wont compile
ol.add("I dont fit in");
数组与泛型之间的第二大区别在于,数组是具体化的reified。因此数组会在运行时才知道并检查他们的元素类型约束。相比之下,泛型则是通过擦除来实现的。因此泛型只在编译时强化它们的类型信息,并在运行时丢弃或者擦除它们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码随意进行互用。
由于上述这些根本的区别,因此数组和泛型不能很好地混合使用。例如,创建泛型、参数化类型或者类型参数的数组是非法的。new List<E>[]
,new List<String>[]
和new E[]
都是非法的。这些在编译时会导致一个generic array creation
泛型数组创建错误。
为什么创建泛型数组是非法的?因为它不是类型安全的。要是它合法,编译器在其他正确的程序中发生的转换就会在运行时失败,并出现一个ClassCastException。
从技术的角度来说,像E
、List<E>
和List<String>
这样的类型应称作不可具体化的类型non-reifiable。直观的说,不可具体化的类型是指其运行时表示法包含的信息比它的编译时表示法包含的信息更少的类型。唯一可具体化的参数化类型是无限制的通配符类型,如List<?>
和Map<?,?>
。虽然不常用,但是创建无限制通配符类型的数组是合法的。
当你得到泛型数组创建错误时,最好的解决办法通常是优先使用集合类型List<E>
,而不是数组类型E[]
。
总而言之,数组和泛型有着非常不同的类型规则。数组是协变且可以具体化的;泛型是不可变的且可以被擦除的。因此,数组提供了运行时的类型安全,但是没有编译时的类型安全,反之,对于泛型也一样。一般来说,数组和泛型不能很好地混合使用。如果你发现自己将它们混合起来使用,并且得到了编译时错误或者警告,你的第一反应就应该是用列表代替数组。
26.优先考虑泛型
27.优先考虑泛型方法
public static <T extends Comparable<T>> T max(List<T> list) {
Iterator<T> i = list.iterator();
T result = i.next();
while(i.hasNext()) {
T t = i.next();
if(t.compareTo(result) > 0) {
result = t;
}
}
return result;
}
28.利用有限制通配符来提升API的灵活性
参数化类型是不可变的invarriant。换句话说,对于任何两个截然不同的类型Type1
和Type2
而言,List<Type1>
既不是List<Type2>
的子类型,也不是它的超类型。
Iterable<? extends E>
确定了子类型subtype后,每个类型便都是自身的子类型,即使它没有将自身扩展。
Collection<? super E>
E的某种超类的集合,E是它自身的一个超类。
为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型。如果某个输入参数既是生产者,又是消费者,那么通配符类型对你就没有什么好处了:因为你需要的是严格的类型匹配,这是不用任何通配符而得到的。
PECS
表示producer-extends,consumer-super
。
换句话说,如果参数化类型表示一个T生产者,就是用<? extends T>
;如果它表示一个T消费者,就使用<? super T>
。
如果编译器不能推断你希望它拥有的类型,可以通过一个显式的类型参数来告诉它要使用哪种类型。
Set<Number> numbers = Union.<Number>union(integers, doubles);
public static <T extends Comparable<? super T>> T max(List<? extends T> list){}
如果类型参数只在方法声明中出现一次,就可以用通配符取代它。如果是无限制的类型参数,就用无限制的通配符取代它,如果是有限制的类型参数,就用有限制的通配符取代它。
所有的comparable和comparator都是消费者。
29.优先考虑类型安全的异构容器
类的类型从字面上来看不再只是简单的Class,而是Class<T>
,例如,String.class
属于Class<String>
类型,Integer.class
属于Class<Integer>
类型。当一个类的字面文字被用在方法中,来传达编译时和运行时的类型信息时,就被称作type token。
public class Favorites {
public <T> void putFavorite(Class<T> type, T instance);
public <T> T getFavorite(Class<T> type);
}
Favorites
实例是类型安全的,当你向它请求String的时候,它从来不会返回一个Integer给你。同时它也是异构的:不像普通的map,它的所有键都是不同类型的。因此,我们将Favorites
称作类型安全的异构容器。
Class.cast
将对象引用动态地转换成Class对象所表示的类型。
Class.asSubclass
将调用它的Class对象转换成用其参数表示的类的一个子类。如果转换成功,该方法返回它的参数;如果失败,则抛出ClassCastException异常。