public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITAL_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;
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if(elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
这个类是泛型化的主要备选对象,换句话说,可以适当的强化这个类来利用泛型。根据实际情况来看,必须转换从堆栈中弹出的对象,以及可能在运行时失败的那些转换。将类泛型化的第一个步骤,就是给他的声明添加一个或者多个类型参数。在这个例子中有一个类型参数,它表示堆栈的元素类型,这个参数的名称通常为E(将这个类强化为带一个或多个泛型参数的类)
下一步是用相应的类型参数替换所有的Object类型,然后试着编译最终的程序
public class StaticGerneric
public E[] elements;
private int size = 0;
private static final int DEFAULT_INITAIL_CAPACITY = 3;
public StaticGerneric() {
elements = new E[DEFAULT_INITAIL_CAPACITY];//报错Cannot create a generic array of E
}
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;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
如黑体所示的方法中报错
如第25条所描述的那样,咱们不能直接创建一个不可具体化类型的数组,如E.当咱们编写支持泛型的数组的时候,都会有这个问题,解决这个问题的方式有两种:
1. 直接绕开创建泛型数组的禁令,不创建泛型数组,直接创建一个Object的数组,并将它转换为泛型数组类型,然后我们看到错误消失了,但是编译器仍然出现了一条警告。这种用法是合法的,但(整体上而言)不是类型安全的:
Type safety: Unchecked cast from Object[] to E[] 转换警告
编译器不可能证明当前程序是类型安全的,但是咱们自己可以证明。咱们必须确保未受检的转换不能危及到程序的类型安全性。当前的数组也就是 elements变量是保存在一个私有的域中的,绝不会被返回到客户端,或传给任何其他方法。这个数组中保存的唯一元素,是传给push方法的那些元素,他们的类型为E,因此未受检的转换不会有任何危害。(这个例子中的数组是不会直接返回给其他方法或者客户端的,返回的只是传进来的数组元素)
证明了未受检的转换时安全的,要尽可能的小的范围中禁止警告(24条),在当前情况下,构造器只包含未受检的数组创建,因此可以在整个构造器中禁止这条警告,通过增加一条注解来完成禁止,Stack能够正确无误的进行编译,你就可以使用它了,无需显式的转换,也无需担心出现ClassCastExecption异常:
@SuppressWarnings("unchecked")
public StaticGerneric() {
// elements = new E[DEFAULT_INITAIL_CAPACITY];
elements = (E[])new Object[DEFAULT_INITAIL_CAPACITY];
}
消除Stack中泛型数组创建错误的第二种方法是:将elements域的类型从E[]改为Object[]。这么做会得到一条不同错误:
public class StaticGerneric1
public Object[] elements;
private int size = 0;
private static final int DEFAULT_INITAIL_CAPACITY = 3;
public StaticGerneric1() {
elements = new Object[DEFAULT_INITAIL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0)
throw new EmptyStackException();
E result = elements[--size];//编译报错Type mismatch: cannot convert from Object to E
elements[size] = null;
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
词句代码将会编译错误;通过把数组中获取到的元素由Object转换成E,可以将错误变为一条警告:
Type mismatch: cannot convert from Object to E
由于E是一个不可具体化的类型,编译器无法再运行时检验转换,你还是可以自己证实未受检的转换时安全的,因此可以禁止该警告,我们只要在包含未受检转换的任务上禁止警告,而不是在整个pop方法上就可以了,如下:
public E pop() {
if (size == 0)
throw new EmptyStackException();
@SuppressWarnings("unchecked")
E result = (E)elements[--size];
elements[size] = null;
return result;
}
具体选择哪种方法来处理泛型数组创建错误,则主要看个人的偏好了,所有其他的东西都一样,但是禁止数组类型的未受检转换比禁止标量类型的更加危险,所以建议采用第二种,(第一个方法是直接对数组的类型进行强转,第二个方法是对数组中元素进行转换)但是在比Stack更实际的泛型类中,或许代码中会有多个地方需要从数组中读取元素,因此选择第二种方法需要多次转换成E,而不是只转换E[],,这也是第一种方法之所以更常用的原因。
下面程序示范了泛型Stack类的使用,程序以相反的顺序打印出他的命令行参数,并转换成大写字母。如果要在从堆栈中弹出的元素上调用String的toUpperCase方法,并不需要显式的转换,并且会确保自动生成的转换成功:
绝大多数泛型就像我们Stack一样,因为他们的类型参数没有限定,你可以创建Stack
有一些泛型限制了可允许的类型参数值,例如:考虑java.util.concurrent.DelayQueue,其声明如下:
class DelayQueue implements BlockingQueue;
类型参数列表要求实际的类型参数E必须是java.util.concurrent.Delayed的一个子类型
它允许DelayedQueue实现及其客户端在DelayedQueue的元素上利用Delayed方法,无需显式转换,也没有出现ClassCastExecption异常的风险。类型参数E被称作有限制的类型参数,注意:子类型关系确定了,每个类型都是他的子类型,因此创建DelayQueue是合法的。
总而言之,使用泛型比使用需要在客户端代码中进行类型转换的类型来的更加安全,也更加容易。再设计新类型的时候,更确保他们不需要这种类型转换就可以使用。这通常意味着要把类做成泛型的,这对于这些类型的新用户来说会变得更加轻松,又不会破坏现有的客户端程序
总结
1,使用泛型比使用需要在客户端代码中进行类型转换的类型来的更加安全,也更加容易,所以咱们涉及类的时候,尽量做成有泛型的