北京北大青鸟学校学术部讲师将继续讲解关于Java泛型的技术知识,关于Java泛型的定义、相关例子等请参考之前的几篇文章,在这里就不做陈述了。今天这篇文章,将主讲介绍一下Java类库中的泛型(北大青鸟课程)
集合类
北京北大青鸟学校专家介绍:到目前为止,Java 类库中泛型支持存在最多的地方就是集合框架。就像容器类是 C++ 语言中模板的主要动机一样(参阅 附录 A:与 C++ 模板的比较)(尽管它们随后用于很多别的用途),改善集合类的类型安全是 Java 语言中泛型的主要动机。集合类也充当如何使用泛型的模型,因为它们演示了泛型的几乎所有的标准技巧和方言。(北大青鸟课程)
所有的标准集合接口都是泛型化的 —— Collection
集合类也使用泛型的许多“技巧”和方言,比如上限通配符和下限通配符。例如,在接口 Collection
interface Collection
boolean addAll(Collection extends V>);
}
该定义组合了通配符类型参数和有限制类型参数,允许您将 Collection
北京北大青鸟学校专家提醒:如果类库将 addAll() 定义为接受 Collection
应该可以将 addAll() 的类型参数定义为 Collection
作为泛型化一个类(如果不小心的话)如何会更改其语义的一个更加微妙的例子,注意 Collection.removeAll() 的参数的类型是 Collection>,而不是 Collection extends V>。这是因为传递混合类型的集合给 removeAll() 是可接受的,并且更加限制地定义 removeAll 将会更改方法的语义和有用性。(北大青鸟课程)
其他容器类
北京北大青鸟学校专家介绍:除了集合类之外,Java 类库中还有几个其他的类也充当值的容器。这些类包括 WeakReference、SoftReference 和 ThreadLocal。它们都已经在其包含的值的类型上泛型化了,所以 WeakReference
泛型不止用于容器
泛型最常见最直观的使用是容器类,比如集合类或引用类(比如 WeakReference
像 Comparable
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
if (t1.compareTo(t2) > 0)
return t1;
else
return t2;
}
北京北大青鸟学校专家介绍:在本例中,您定义了一个泛型方法,在类型 T 上泛型化,您约束该类型扩展(实现) Comparable
更好的是,编译器将使用类型推理来确定当调用 max() 时 T 的值表示什么意思。所以根本不用指定 T,下面的调用就能工作:
String s = max("moo", "bark");
编译器将计算出 T 的预定值是 String,因此它将进行编译和类型检查。但是如果您试图用不实现 Comparable
Class
类 Class 已经泛型化了,但是很多人一开始都感觉其泛型化的方式很混乱。Class
在以前的 JDK 中,Class.newInstance() 方法的定义返回 Object,您很可能要将该返回类型强制转换为另一种类型:
class Class {
Object newInstance();
}
但是使用泛型,您定义 Class.newInstance() 方法具有一个更加特定的返回类型:
class Class
T newInstance();
}(北大青鸟课程)
如何创建一个 Class
让 Foo.class 是 Class
考虑一个方法,它从数据库检索一组对象,并返回 JavaBeans 对象的一个集合。您通过反射来实例化和初始化创建的对象,但是这并不意味着类型安全必须完全被抛至脑后。考虑下面这个方法:
public static
// Use Selector to select rows
List
for (/* iterate over results */) {
T row = c.newInstance();
// use reflection to set fields from result
list.add(row);
}
return list;
}
(北大青鸟课程)
可以像下面这样简单地调用该方法:
List
编译器将会根据 FooRecord.class 是 Class
用 Class
Collection 接口包含一个方法,用于将集合的内容复制到一个调用者指定类型的数组中:
public Object[] toArray(Object[] prototypeArray) { ... }
toArray(Object[]) 的语义是,如果传递的数组足够大,就会使用它来保存结果,否则,就会使用反射分配一个相同类型的新数组。一般来说,单独传递一个数组作为参数来提供想要的返回类型是一个小技巧,但是在引入泛型之前,这是与方法交流类型信息最方便的方式。
有了泛型,就可以用一种更加直观的方式来做这件事。不像上面这样定义 toArray(),泛型 toArray() 可能看起来像下面这样:
public
调用这样一个 toArray() 方法很简单:
FooBar[] fba = something.toArray(FooBar.class);
Collection 接口还没有改变为使用该技术,因为这会破坏许多现有的集合实现。但是如果使用泛型从新构建 Collection,则当然会使用该方言来指定它想要返回值是哪种类型。
Enum
JDK 5.0 中 Java 语言另一个增加的特性是枚举。当您使用 enum 关键字声明一个枚举时,编译器就会在内部为您生成一个类,用于扩展 Enum 并为枚举的每个值声明静态实例。所以如果您说:
public enum Suit {HEART, DIAMOND, CLUB, SPADE};
编译器就会在内部生成一个叫做 Suit 的类,该类扩展 java.lang.Enum
与 Class 一样,Enum 也是一个泛型类。但是与 Class 不同,它的签名稍微更复杂一些:
class Enum
这究竟是什么意思?这难道不会导致无限递归?
我们逐步来分析。类型参数 E 用于 Enum 的各种方法中,比如 compareTo() 或 getDeclaringClass()。为了这些方法的类型安全,Enum 类必须在枚举的类上泛型化。
所以 extends Enum
北京北大青鸟学校专家介绍,该部分又具有两个部分。第一部分指出,作为 Enum 的类型参数的类本身必须是 Enum 的子类型,所以您不能声明一个类 X 扩展 Enum
总之,Enum 是一个参数化的类型,只可以为它的子类型实例化,并且这些子类型然后将根据子类型来继承方法。幸运的是,在 Enum 情况下,编译器为您做这些工作,一切都很好。(北京北大青鸟学校,未完待续)