Java泛型总结(三)

2014-11-24 03:08:01 · 作者: · 浏览: 17

考虑例子 Matrix 类,它使用类型参数 V,该参数由 Number 类来限制:
public class Matrix { ... }
编译器允许您创建 Matrix 或 Matrix 类型的变量,但是如果您试图定义 Matrix 类型的变量,则会出现错误。类型参数 V 被判断为由 Number 限制 。在没有类型限制时,假设类型参数由 Object 限制。这就是为什么前一屏 泛型方法 中的例子,允许 List.get() 在 List< > 上调用时返回 Object,即使编译器不知道类型参数 V 的类型。
5. 一个简单的泛型类
5.1 编写基本的容器类
此时,您可以开始编写简单的泛型类了。到目前为止,泛型类最常见的用例是容器类(比如集合框架)或者值持有者类(比如 WeakReference 或 ThreadLocal)。我们来编写一个类,它类似于 List,充当一个容器。其中,我们使用泛型来表示这样一个约束,即 Lhist 的所有元素将具有相同类型。为了实现起来简单,Lhist 使用一个固定大小的数组来保存值,并且不接受 null 值。
Lhist 类将具有一个类型参数 V(该参数是 Lhist 中的值的类型),并将具有以下方法:
public class Lhist {
public Lhist(int capacity) { ... }
public int size() { ... }
public void add(V value) { ... }
public void remove(V value) { ... }
public V get(int index) { ... }
}
要实例化 Lhist,只要在声明时指定类型参数和想要的容量:
Lhist stringList = new Lhist(10);
5.2 实现构造函数
在实现 Lhist 类时,您将会遇到的第一个拦路石是实现构造函数。您可能会像下面这样实现它:
public class Lhist {
private V[] array;
public Lhist(int capacity) {
array = new V[capacity]; // illegal
}
}
这似乎是分配后备数组最自然的一种方式,但是不幸的是,您不能这样做。具体原因很复杂,当学习到底层细节一节中的“擦除”主题时,您就会明白。分配后备数组的实现方式很古怪且违反直觉。下面是构造函数的一种可能的实现(该实现使用集合类所采用的方法):
public class Lhist {
private V[] array;
public Lhist(int capacity) {
array = (V[]) new Object[capacity];
}
}
另外,也可以使用反射来实例化数组。但是这样做需要给构造函数传递一个附加的参数 —— 一个类常量,比如 Foo.class。后面在 Class 一节中将讨论类常量。
5.3 实现方法
实现 Lhist 的方法要容易得多。下面是 Lhist 类的完整实现:
public class Lhist {
private V[] array;
private int size;
public Lhist(int capacity) {
array = (V[]) new Object[capacity];
}
public void add(V value) {
if (size == array.length)
throw new IndexOutOfBoundsException(Integer.toString(size));
else if (value == null)
throw new NullPointerException();
array[size++] = value;
}
public void remove(V value) {
int removalCount = 0;
for (int i=0; i if (array[i].equals(value))
++removalCount;
else if (removalCount > 0) {
array[i-removalCount] = array[i];
array[i] = null;
}
}
size -= removalCount;
}
public int size() { return size; }
public V get(int i) {
if (i >= size)
throw new IndexOutOfBoundsException(Integer.toString(i));
return array[i];
}
}
注意,您在将会接受或返回 V 的方法中使用了形式类型参数 V,但是您一点也不知道 V 具有什么样的方法或域,因为这些对泛型代码是不可知的。
5.4 使用 Lhist 类
使用 Lhist 类很容易。要定义一个整数 Lhist,只需要在声明和构造函数中为类型参数提供一个实际值即可:
Lhist li = new Lhist(30);
编译器知道,li.get() 返回的任何值都将是 Integer 类型,并且它还强制传递给 li.add() 或 li.remove() 的任何东西都是 Integer。除了实现构造函数的方式很古怪之外,您不需要做任何十分特殊的事情以使 Lhist 是一个泛型类。
6. Java类库中的泛型
6.1 集合类
到目前为止,Java 类库中泛型支持存在最多的地方就是集合框架。就像容器类是 C++ 语言中模板的主要动机一样(参阅附录 A:与 C++ 模板的比较)(尽管它们随后用于很多别的用途),改善集合类的类型安全是 Java 语言中泛型的主要动机。集合类也充当如何使用泛型的模型,因为它们演示了泛型的几乎所有的标准技巧和方言。
所有的标准集合接口都是泛型化的 —— Collection、List、Set 和 Map。类似地,集合接口的实现都是用相同类型参数泛型化的,所以 HashMap 实现 Map 等。
集合类也使用泛型的许多“技巧”和方言,比如上限通配符和下限通配符。例如,在接口 Collection 中,addAll 方法是像下面这样定义的:
interface Collection {
boolean addAll(Collection< extends V>);
}
该定义组合了通配符类型参数和有限制类型参数,允许您将 Collection 的内容添加到 Collection
如果类库将 addAll() 定义为接受 Collection,您就不能将 Collection 的内容添加到 Collection。不是限制 addAll() 的参数是一个与您将要添加到的集合包含相同类型的集合,而有可能建立一个更合理的约束,即传递给 addAll() 的集合的元素 适合于添加到您的集合。有限制类型允许您这样做,并且使用有限制通配符使您不需要使用另一个不会用在其他任何地方的占位符名称。
应该可以将 addAll() 的类型参数定义为 Collection。但是,这不但没什么用,而且还会改变 Collection 接口的语义,因为泛型版本的语义将会不同于非泛型版本的语义。这阐述了泛型化一个现有的类要比定义一个新的泛型类难得多,因为您必须注意不要更改类的语义或者破坏现有的非泛型代码。
作为泛型化一个类(如果不小心的话)如何会更改其语义的一个更加微妙的例子,注意 Collection.removeAll() 的参数的类型是 Collection< >,而不是 Collection< extends V>。这是因为传递混合类型的集合给 removeAll() 是可接受的,并且更加限制地定义 removeAll 将会更改方法的语义和有用性。