Effective Java 第三版——29. 优先考虑泛型

Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化。
在这里第一时间翻译成中文版。供大家学习分享之用。

Effective Java, Third Edition

29. 优先考虑泛型

参数化声明并使用JDK提供的泛型类型和方法通常不会太困难。 但编写自己的泛型类型有点困难,但值得努力学习。

考虑条目 7中的简单堆栈实现:

// Object-based collection - a prime candidate for generics public class Stack { private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; // Eliminate obsolete reference return result; } public boolean isEmpty() { return size == 0; } private void ensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } }

这个类应该已经被参数化了,但是由于事实并非如此,我们可以对它进行泛型化。 换句话说,我们可以参数化它,而不会损害原始非参数化版本的客户端。 就目前而言,客户端必须强制转换从堆栈中弹出的对象,而这些强制转换可能会在运行时失败。 泛型化类的第一步是在其声明中添加一个或多个类型参数。 在这种情况下,有一个类型参数,表示堆栈的元素类型,这个类型参数的常规名称是E(条目 68)。

下一步是用相应的类型参数替换所有使用的Object类型,然后尝试编译生成的程序:

// Initial attempt to generify Stack - won't compile! 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; // Eliminate obsolete reference return result; } ... // no changes in isEmpty or ensureCapacity }

你通常会得到至少一个错误或警告,这个类也不例外。 幸运的是,这个类只产生一个错误:

Stack.java:8: generic array creation elements = new E[DEFAULT_INITIAL_CAPACITY]; ^

如条目 28所述,你不能创建一个不可具体化类型的数组,例如类型E。每当编写一个由数组支持的泛型时,就会出现此问题。 有两种合理的方法来解决它。 第一种解决方案直接规避了对泛型数组创建的禁用:创建一个Object数组并将其转换为泛型数组类型。 现在没有了错误,编译器会发出警告。 这种用法是合法的,但不是(一般)类型安全的:

Stack.java:8: warning: [unchecked] unchecked cast found: Object[], required: E[] elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; ^

编译器可能无法证明你的程序是类型安全的,但你可以。 你必须说服自己,不加限制的类型强制转换不会损害程序的类型安全。 有问题的数组(元素)保存在一个私有属性中,永远不会返回给客户端或传递给任何其他方法。 保存在数组中的唯一元素是那些传递给push方法的元素,它们是E类型的,所以未经检查的强制转换不会造成任何伤害。

一旦证明未经检查的强制转换是安全的,请尽可能缩小范围(条目 27)。 在这种情况下,构造方法只包含未经检查的数组创建,所以在整个构造方法中抑制警告是合适的。 通过添加一个注解来执行此操作,Stack可以干净地编译,并且可以在没有显式强制转换或担心ClassCastException异常的情况下使用它:

// The elements array will contain only E instances from push(E). // This is sufficient to ensure type safety, but the runtime // type of the array won't be E[]; it will always be Object[]! @SuppressWarnings("unchecked") public Stack() { elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; }

消除Stack中的泛型数组创建错误的第二种方法是将属性元素的类型从E []更改为Object []。 如果这样做,会得到一个不同的错误:

Stack.java:19: incompatible types found: Object, required: E E result = elements[--size]; ^

可以通过将从数组中检索到的元素转换为E来将此错误更改为警告:

Stack.java:19: warning: [unchecked] unchecked cast found: Object, required: E E result = (E) elements[--size]; ^

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgxdg.html