JDK1.5引入了泛型,在泛型引入之前,从集合中读取到的每个对象都必须进行转换,如果不小心引入了错误的对象,在运行时就会出错。有了泛型之后,编译器会在编译阶段检查你是否插入了错误的对象。这样是程序更加安全。
第23条:请不要在新代码中使用原生态类型
所谓的原生态类型,就是不太任何实际类型参数的泛型名称,例如List<E>
相对应的原生态类型就是List。
本条说的主要是使用通配符的要点,可以参考这几篇文:
第24条:消除非受检警告
消除编译期出现的警告,可以避免的运行时出现ClassCastException异常。
如果无法消除警告。同时可以证明引起警告的代码安全的,才可以使用一个@SuppressWarning("unchecked")
注解来禁止这条警告。
被@SuppressWarning("unchecked")
注解警告的代码范围应该尽可能的小,不要在整个类上加这个注解。
第25条:列表优先于数组
列表和数组的两个主要不同点:
1、数组是协变的(即如果Sub是Super的子类型,那么数组类型Sub[]就是Super[]的子类型),而泛型是不可变的(即对于任意两个不同的类型Type1和Type2,List<Type1>
既不是List<Type2>
的子类型,也不是List<Type2>
的超类型)。
看个栗子:
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in";
上面程序会在编译时通过,但运行时失败。而下面的代码会在编译时失败:
List<Object> o1 = new ArrayList<Long>();//编译失败
o1.add("I don't fit in");
第二个区别是:数组是具体化的,因此数组在运行时才知道并检查它们的元素类型约束。而泛型只在编译时强化它们的类型信息。
正是由于这些区别,导致数组和泛型不能很好混用,下面这些都是非法操作:
//非法
new List<E>[]
new List<String>[]
new E[]
为什么泛型数组会出错,下面通过示例说明:
//创建了一个泛型数组,这是非法的
List<String>[] stringLists = new List<String>[1];//(1)
//创建并初始化了一个包含单个元素的List<Integer>,合法
List<Integer> intList = Arrays.asList(42);//(2)
//将List<Stirng>数组保存到一个Object数组变量中,这是合法的
Object[] objects = stringLists;//(3)
//将List<String>保存到Object数组的位移元素中,合法
objects[0] = intList;//(4)
string s = stringLists[0].get(0);//(5)
我们将一个List<Integer>
实例保存到了原本声明只包含List<String>
实例的数组中。在(5)中我们从这个数组中获取列表的唯一元素,编译器自动地将获取到的元素转换成String,但它是个Integer。于是抛出一个ClassCastException异常,所以在第一行会产生一个编译错误。
总之,数组和泛型游着非常不同的类型规则。数组是协变且可以具体化的;但是泛型是不可协变的且可以被排除的。因此,数组提供了运行时的类型安全,但是没有编译时的类型安全,反之,对于与泛型也一样。一般来说,数组和泛型不能很好地混合使用。如果你发现自己将它们混合起来使用,并且得到了编译时错误或者警告,你的第一反应该是用列表代替数组。
第26条:优先考虑泛型
这是第6条中的堆栈实现:
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack(){
elements = new Objects[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if(size = 0) {
throw new EmptyStackException();
}
return elements[--size];
}
private void ensureCapacity() {
if(elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
可以强化这个类用泛型实现:
public class stack<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack {
//这句话报错
elements = new E[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if(size == 0) {
throw new EmptyStackException();
}
E result = elements[--size];
elements[size] = null;
return result;
}
}
在上面这个类中,elements = new E[DEFAULT_INITIAL_CAPACITY];
这句话会报错。因为不能创建不可具体化的类型数组。如E。
解决方法一:创建一个Object[]数组,强转为E[]:
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
这会产生一个警告(类型转化的警告),但是我们确信这是合法的,所以可以加注解消除警告:
@SupressWarning("uncheked")
Stack {
...
}
第二种解决方式:将E[] 改为Object[],并进行强转:
E result = (E) elemments[--size];
最后示范泛型Stack类的使用。程序以相反的顺序打印出它的命令行参数,并转换成大写字母:
public static void main(String[] args) {
Stack<String> stack = new Stack<String>();
for(String arg : args) {
stack.push(args);
}
while(!stack.isEmpty()) {
System.out.println(stack.pop().toUpperCase());
}
}
使用泛型比使用需要在客户端代码中进行转换的类型来的更安全,也更容易,在设计新类的时候,要确保它们不需要这种转换就可以使用。这通常意味着要把类做成泛型的。
第27条:有限考虑泛型方法
下面这个方法,返回两个集合的联合:
public static Set union(Set s1,Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
这会出现警告。为了修正这些警告:
public static <E> Set<E> union(Set<E> s1,Set<E> s2) {
Set<E> result = new HashSet<E>(s1);
result.addAll(s2);
return result;
}
使用上面的方法:
public static void main(String[] args) {
Set<String> guys = new HashSet<String>(Arrays.asList("Tom", "Dick", "Harry"));
Set<String> stooges= new HashSet<String>(Arrays.asList("Larry", "Moe", "Curly"));
}
Set<String> aflcio = union(guy, stooges);
Systen.out.println(aflcio);
泛型方法像泛型一样,使用起来比要求客户端转换输入参数并返回值的方法来的更加安全,也更加容易,在设计泛型方法时,应确保新方法可以不用转换就能使用。
第28条:利用有限制通配符来提升API的灵活性
略
第29条:优先考虑类型安全的异构容器
略