Effective Java笔记第四章泛型
第四节优先考虑泛型
下面我们举个例子,将他作为泛型化的主要备选对象,换句话说,可以适当的强化这个类来利用泛型。
public class Stack {
private Object[] element;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 1;
public Stack() {
element = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
element[size++] = e;
}
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
Object result = element[--size];
element[size] = null;
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (element.length == size) {
element = Arrays.copyOf(element, 2 * size + 1);
}
}
}
首先我们代码进行简单的转换,先用类型参数替换所有的Object类型:
public class StackImprove1<E> {
private E[] element;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 1;
public StackImprove() {
//这里会爆编译错误
element=new E[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
element[size++] = e;
}
public E pop() {
if (size == 0) {
throw new EmptyStackException();
}
E result = element[--size];
element[size] = null;
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (element.length == size) {
element = Arrays.copyOf(element, 2 * size + 1);
}
}
}
以上代码是有错误的,你不可能创建不可具体化类型的数组。解决这个问题有两种方法:
1)直接绕过创建泛型数组的禁令:创建一个Object的数组,并将他转换成泛型数组类型。这样的话错误是消除了,但是产生了一条警告。表示这种用法是合法的,但是不是类型安全的。
编译器不可能证明你的程序是类型安全的,但是你可以证明。你必须确保未受检的转换不会危及到程序的类型安全性。相关的数组保存在一个私有的域中,永远不会被返回到客户端,或者传递给任何其他方法。这个数组中保存的唯一元素,是传给push方法的那些元素,他们的类型为E,因此未受检的转换不会有任何危害。
一旦你证明了未受检的转换是安全的,就要在尽可能小的范围中禁止警告。
下面我们对上面的代码进行第一种方式的改造:
public class StackImprove1<E> {
private E[] element;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 1;
// public StackImprove() {
// element=new E[DEFAULT_INITIAL_CAPACITY];
// }
//The elements array will contain only E instances from push(E) 元素数组将只包含来自push(E)的E个实例
//This is sufficient to ensure type safety ,but the runtime 这只能在运行时确保安全
//type of the array was not be E[] ; it will always be Object[] 数组的类型不是E[];它将始终是Object[]
@SuppressWarnings("unchecked")
public StackImprove1() {
element=(E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
element[size++] = e;
}
public E pop() {
if (size == 0) {
throw new EmptyStackException();
}
E result = element[--size];
element[size] = null;
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (element.length == size) {
element = Arrays.copyOf(element, 2 * size + 1);
}
}
}
2)将elements域的类型从E[ ]改为Object[ ],之后把数组中获得的元素由Object转换为E。由于E是一个不可具体化的类型,编译器无法在运行时检验转换。你还是可以自己证实未受检的转换是安全的,因此可以禁止该警告。
下面我们对上面的代码进行第二种方式的改造:
public class StackImprove2<E> {
private Object[] element;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 1;
public StackImprove2() {
element = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
element[size++] = e;
}
//Appropriate suppression of unchecked warning 在尽可能小的范围使用禁止警告
public E pop() {
if (size == 0) {
throw new EmptyStackException();
}
//push requires elements to be of type E,so cast is correct
@SuppressWarnings("unchecked") E result =
(E) element[--size];
element[size] = null;
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (element.length == size) {
element = Arrays.copyOf(element, 2 * size + 1);
}
}
public static void main(String[] args) {
StackImprove2<String> stringStack=new StackImprove2<>();
List<String> stringList=new ArrayList<>();
stringList.add("cc");
stringList.add("dd");
for (String s : stringList) {
stringStack.push(s);
}
System.out.println(stringStack.element.length);
while (!stringStack.isEmpty()){
System.out.println(stringStack.pop().toUpperCase());
}
}
}
具体选择这两种方法中的哪一种来处理泛型数组创建错误,则主要看个人偏好了。所有其他的东西都一样,但是禁止数组类型的未受检转换比标量类型的更加危险,所以建议采用第二种方法。但是在实际的泛型类中,或许代码中会有多个地方需要从数组中读取元素,因此选择第二种方案需要多次转换成E,而不是只转换成E[ ],这也是第一种方案之所以更常用的原因。
类型参数列表(E extends ?),类型参数E被称为有限制的类型参数。注意,子类型关系确定了,每个类型都是他自身的子类型。
总之,使用泛型比使用需要在客户端代码中进行转换的类型来的更加安全,也更加容易。在设计新类型的时候,要确保他们必须要这种转换就可以使用,这通常意味着要把类做成泛型的,只要时间允许,就把现有的类型都泛型化。这对于这些类型的新用户来说更加轻松,又不会破坏现有的客户端。