? ? ?
? ? ? ?一般来说,将集合声明参数化,以及使用JDK所提供的泛型和泛型方法,这些都不太困难。编写自己的泛型会比较困难一些,但是值得花时间去学习如何编写?
?
?
class="java" name="code">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 s){ ensuerCapacity(); elements[size++] = s; } public Object pop(){ if (size == 0) { throw new EmptyStackException(); } Object result = elements[--size]; elements[size] = null;//消除过时的引用 return elements[--size]; } private void ensuerCapacity(){ if (elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } } }
?
?
? ? ? ?这个类是泛型化的主要备选对象,换句话说,可以适当的强化这个类来利用泛型。根据实际情况来看,必须转换从堆栈弹出的对象,以及可能运行时失败的那些转换。将类泛型化的第一个步骤是给他们声明一个或者多个类型的参数,在这个示例中有一类型参数,他表示堆栈的元素类型,这个参数的名称通常为 E。
?
下一步是用相应的类型参数替换所有的Object类型,然后试着编译最终程序。
?
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 s){ ensuerCapacity(); elements[size++] = s; } 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 ensuerCapacity(){ if (elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } } }
通常,你将至少得到一个错误或警告,这个类也不例外。还好,这个类值产生了一个错误如下:
?
Cannot create a generic array of E
?
elements = new E[DEFAULT_INITIAL_CAPACITY];
?
?
?
? ? ? 如说,你不能创建不可具体化的类型的数组,如E,每当编写数组 支持的泛型时,都会出现这个问题,。解决这个问题有两个方法,第一种:直接绕过创建泛型数组的禁令,创建一个Object[]数组,并将它转换成泛型数组类型。现在错误是消除了,但是编译器又会产生一个警告,这种用法是合法的,但不是类型安全的
?
elements = (E[])new E[DEFAULT_INITIAL_CAPACITY];
?
?
? ? ? 编译器不能证明你的程序是安全的,但是你可以证明。你自己必须确保未受检的转换不会危害到程序的类型的安全性,相关的数组保存在一个私有的域中,永远不会被返回到客户端,或者转给任何其他方法。这个数组中保存的唯一元素,是转给push方法的那个元素,他们的类型为E,因此未受检的转换不会有任何危险,
?
一旦你证明了未受检的转换时安全的,你要尽可能的小的范围中禁止警告,在这种情况下,构造器只包含未受检的数组创建,因此可以在整个构造器中禁止这条警告,通过增加一条注解来完成禁止,Stack能够正确无误的进行编译,你就可以使用它了,无需显式的转换,唯一无需担心出现ClassCastExecption异常:
?
@SuppressWarnings("unchecked") public Stack(){ elements = (E[])new E[DEFAULT_INITIAL_CAPACITY]; }
?
?
? ? ? ?消除Stack中泛型数组创建错误的第二种方法是:将elements域的类型从E[]改为Object[]。这么做会得到一条不同错误:
?
E result = elements[size--];
?词句代码将会编译错误;通过把数组中获取到的元素有Object转换成E,可以将这一条错误编程一条警告:
?
?
E result = (E) elements[--size];
?
?
? ? ? 由于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方法,并不需要显式的转换,并且会确保自动生成的转换会成功。
?
public static void main(String[] args) { Stack<String> stack = new Stack<String>(); for (String arg : args) { stack.push(arg); } while(!stack.isEmpty()){ System.out.println(stack.pop().toUpperCase()); } }
?
? ? ? ?绝大多数泛型就像我们Stack一样,因为他们的类型参数没有限定,你可以创建Stack<Object>、Stack<int[]>、Stack<List<String>>,或者任何其他对象引用类型的Stack,注意不能创建基本类型的Satck:企图创建Stack<int>或者Satck<double>会产生一个编译错误。这是JAVA泛型系统根本的局限性,你可以通过使用基本类型的包装类来避开这个局限性。
?
? ? ? ?有一些泛型限制了可允许的类型参数值,例如:考虑java.util.concurrent.DelayQueue,其声明如下:
class DelayQueue<E extends Delayed> implements BlockingQueue<E>;
?
? ? ? 类型参数列表<E extends Delayed>要求实际的类型参数E必须是java.util.concurrent.Delayed的一个子类型
它允许DelayedQueue实现及其客户端在DelayedQueue的元素上利用Delayed方法,无需显式转换,也没有出现ClassCastExecption异常的风险。类型参数E被称作有限制的类型参数,注意:子类型关系确定了,每个类型都是他的子类型,因此创建DelayQueue<Delayed>是合法的。
?
? ? ? ?总而言之,使用泛型比使用需要在客户端代码中进行类型转换的类型来的更加安全,也更加容易。再设计新类型的时候,更确保他们不需要这种类型转换就可以使用。这通常意味着要把类做成泛型的,这对于这些类型的新用户来说会变得更加轻松,又不会破坏现有的客户端程序。
?
?
?