Java泛型总结(四)

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

6.2 其他容器类
除了集合类之外,Java 类库中还有几个其他的类也充当值的容器。这些类包括 WeakReference、SoftReference 和 ThreadLocal。它们都已经在其包含的值的类型上泛型化了,所以 WeakReference 是对 T 类型的对象的弱引用,ThreadLocal 则是到 T 类型的线程局部变量的句柄。
6.3 泛型不止用于容器
泛型最常见最直观的使用是容器类,比如集合类或引用类(比如 WeakReference)。Collection 中类型参数的含义很明显 —— “一个所有值都是 V 类型的集合”。类似地,ThreadLocal 也有一个明显的解释 —— “一个其类型是 T 的线程局部变量”。但是,泛型规格说明中没有指定容积。
像 Comparable 或 Class 这样的类中类型参数的含义更加微妙。有时,就像 Class 中一样,类型变量主要是帮助编译器进行类型推理。有时,就像隐含的 Enum> 中一样,类型变量只是在类层次结构上加一个约束。
6.3.1 Comparable
Comparable 接口已经泛型化了,所以实现 Comparable 的对象声明它可以与什么类型进行比较。(通常,这是对象本身的类型,但是有时也可能是父类。)
public interface Comparable {
public boolean compareTo(T other);
}
所以 Comparable 接口包含一个类型参数 T,该参数是一个实现 Comparable 的类可以与之比较的对象的类型。这意味着如果定义一个实现 Comparable 的类,比如 String,就必须不仅声明类支持比较,还要声明它可与什么比较(通常是与它本身比较):
public class String implements Comparable { ... }
现在来考虑一个二元 max() 方法的实现。您想要接受两个相同类型的参数,二者都是 Comparable,并且相互之间是 Comparable。幸运的是,如果使用泛型方法和有限制类型参数的话,这相当直观:
public static > T max(T t1, T t2) {
if (t1.compareTo(t2) > 0)
return t1;
else
return t2;
}
在本例中,您定义了一个泛型方法,在类型 T 上泛型化,您约束该类型扩展(实现) Comparable。两个参数都必须是 T 类型,这表示它们是相同类型,支持比较,并且相互可比较。容易!
更好的是,编译器将使用类型推理来确定当调用 max() 时 T 的值表示什么意思。所以根本不用指定 T,下面的调用就能工作:
String s = max("moo", "bark");
编译器将计算出 T 的预定值是 String,因此它将进行编译和类型检查。但是如果您试图用不实现 Comparable 的 类 X 的参数调用 max(),那么编译器将不允许这样做。
6.3.2 Class
类 Class 已经泛型化了,但是很多人一开始都感觉其泛型化的方式很混乱。Class 中类型参数 T 的含义是什么?事实证明它是所引用的类接口。怎么会是这样的呢?那是一个循环推理?如果不是的话,为什么这样定义它?
在以前的 JDK 中,Class.newInstance() 方法的定义返回 Object,您很可能要将该返回类型强制转换为另一种类型:
class Class {
Object newInstance();
}
但是使用泛型,您定义 Class.newInstance() 方法具有一个更加特定的返回类型:
class Class {
T newInstance();
}
如何创建一个 Class 类型的实例?就像使用非泛型代码一样,有两种方式:调用方法 Class.forName() 或者使用类常量 X.class。Class.forName() 被定义为返回 Class< >。另一方面,类常量 X.class 被定义为具有类型 Class,所以 String.class 是 Class 类型的。
让 Foo.class 是 Class 类型的有什么好处?大的好处是,通过类型推理的魔力,可以提高使用反射的代码的类型安全。另外,还不需要将 Foo.class.newInstance() 强制类型转换为 Foo。
考虑一个方法,它从数据库检索一组对象,并返回 JavaBeans 对象的一个集合。您通过反射来实例化和初始化创建的对象,但是这并不意味着类型安全必须完全被抛至脑后。考虑下面这个方法:
public static List getRecords(Class c, Selector s) {
// Use Selector to select rows
List list = new ArrayList();
for (/* iterate over results */) {
T row = c.newInstance();
// use reflection to set fields from result
list.add(row);
}
return list;
}
可以像下面这样简单地调用该方法:
List l = getRecords(FooRecord.class, fooSelector);
编译器将会根据 FooRecord.class 是 Class 类型的这一事实,推断 getRecords() 的返回类型。您使用类常量来构造新的实例并提供编译器在类型检查中要用到的类型信息。
6.3.3 用 Class 替换 T[]
Collection 接口包含一个方法,用于将集合的内容复制到一个调用者指定类型的数组中:
public Object[] toArray(Object[] prototypeArray) { ... }
toArray(Object[]) 的语义是,如果传递的数组足够大,就会使用它来保存结果,否则,就会使用反射分配一个相同类型的新数组。一般来说,单独传递一个数组作为参数来提供想要的返回类型是一个小技巧,但是在引入泛型之前,这是与方法交流类型信息最方便的方式。
有了泛型,就可以用一种更加直观的方式来做这件事。不像上面这样定义 toArray(),泛型 toArray() 可能看起来像下面这样:
public T[] toArray(Class returnType)
调用这样一个 toArray() 方法很简单:
FooBar[] fba = something.toArray(FooBar.class);
Collection 接口还没有改变为使用该技术,因为这会破坏许多现有的集合实现。但是如果使用泛型从新构建 Collection,则当然会使用该方言来指定它想要返回值是哪种类型。
6.3.4 Enum
JDK 5.0 中 Java 语言另一个增加的特性是枚举。当您使用 enum 关键字声明一个枚举时,编译器就会在内部为您生成一个类,用于扩展 Enum 并为枚举的每个值声明静态实例。所以如果您说:
public enum Suit {HEART, DIAMOND, CLUB, SPADE};
编译器就会在内部生成一个叫做 Suit 的类,该类扩展 java.lang.Enum 并具有叫做 HEART、DIAMOND、CLUB 和 SPADE 的常量(public static final)成员,每个成员都是 Suit 类。
与 Class 一样,Enum 也是一个泛型类。但是与 Class 不同,它的签名稍微更复杂一些:
class Enum> { . . . }
这究竟是什么意思?这难道