假设 Fooblibar.com 把他们的 API 转换为泛型的, 但有些客户还没有转换。代码就会像下面的: package com.Fooblibar.widgets; public interface Part { ... } publlic class Inventory { /** *Adds a new Assembly to the inventory database. *The assembly is given the name name, and consists of a set *parts specified by parts. All elements of the collection parts *must support the Part interface. **/ public static void addAssembly(String name, Collection parts) {...} public static Assembly getAssembly(String name){ ... } } public interface Assembly { Collection getParts();//Return a collection of Parts } 客户代码如下: package com.mycompany.inventory; import com.Fooblibar.widgets.*; public class Blade implements Part { ... } public class Guillotine implements Part { ... } public class Main { public static void main(String[] args){ Collection c = new ArrayList(); c.add(new Guillotine()); c.add(new Blade()); Inventory.addAssembly("thingee", c);//1: unchecked warning Collection k = Inventory.getAssembly("thingee").getParts(); } } 客户代码是在引进泛型之前写下的,但是它使用了 com.Fooblibar.widgets 包和集 合库,两个现在都是在用泛型的。在客户代码里面使用的泛型全部都是原生类型。 第 1 行产生一个未检测警告,因为把一个原生 Collection 传递给了一个需要 Part 类型的 Collection 的地方,编译器不能保证原生的 Collection 是一个 Part 类型的 Collection。 不这样做的话,你也可以在编译客户代码的时候使用 source 1.4 这个标记来保证不 会产生警告。但是这样的话你就不能使用所有 JDK 1.5 引入的新的语言特性。 免费 linux 公开课,,现在报名! 七、晦涩难懂的部分 七-1 泛型类为所有调用所共享 下面的代码段会打印出什么呢? List l1 = new ArrayList(); List l2 = new ArrayList(); System.out.println(l1.getClass() == l2.getClass()); 你可能会说是 false,但是你错了,打印的是 true,因为所有泛型类的实例它们 的运行时的类(run-time class)都是一样的,不管它们实际类型参数如何。 泛型类之所以为泛型的,是因为它对所有可能的类型参数都有相同的行为,相同 的类可以看作是有很多不同的类型。 结果就是,一个类的静态的变量和方法也共享于所有的实例中,这就是为什么不 允许在静态方法或初始化部分、或者在静态变量的声明或初始化中引用类型参数。 免费 linux 公开课,,现在报名! 七-2 强制类型转换和 instanceof 泛型类在它所有的实例****享,就意味着判断一个实例是否是一个特别调用的泛 型的实例是毫无意义的: Collection cs = new ArrayList(); if (cs instanceof Collection) {...}//非法 类似地,像这样的强制类型转换: Collection cstr = (Collection) cs;//未检测警告 给出了一个未检测的警告,因为这里系统在运行时并不会检测。 对于类型变量也一样: T BadCast(T t, Object o) { return (T) o;//未检测警告 } 类型变量不存在于运行时,这就是说它们对时间或空间的性能不会造成影响。 但也因此而不能通过强制类型转换可靠地使用它们了。 免费 linux 公开课,,现在报名! 七-3 数组 数组对象的组件类型可能不是一个类型变量或一个参数化类型,除非它是一个 (无界的)通配符类型。你可以声明元素类型是类型变量和参数华类型的数组类型, 但元素类型不能是数组对象。 这自然有点郁闷,但这个限制对避免下面的情况是必要的: List[] lsa = new List[10];//实际上是不允许的 Object o = lsa; Object[] oa = (Object[]) o; List li = new ArrayList(); li.add(new Integer(8)); oa[1] = li;//不合理,但可以通过运行时的赋值检测 String s = lsa[1].get(0);//运行时出错:ClassCastException 异常 如果参数化类型的数组允许的话,那么上面的例子编译时就不会有未检测的警告, 但在运行时出错。对于泛型编程,我们的主要设计目标是类型安全,而特别的是这个 语言的设计保证了如果使用了 javac -source 1.5 来编译整个程序而没有未检测的 警告的话,它是类型安全的。 但是你仍然会使用通配符数组,这与上面的代码相比有两个变化。首先是不使用 数组对象或元素类型被参数化的数组类型,这样我们就需要在从数组中取出一个字符 串的时候进行强制类型转换: List[] lsa = new List[10];//没问题,无界通配符类型数组 Object o = lsa; Object[] oa = (Object[]) o; List li = new ArrayList(); li.add(new Integer(3)); oa[1] = li;//正确 String s = (String) lsa[1].get(0);//运行时错误,显式强制类型转换 第二个变化是,我们不创建元素类型被参数化的数组对象,但仍然使用参数化元素 类型的数组类型,这是允许的,但引起现未检测警告。这样的程序实际上是不安全的, 甚至最终会出错。 List[] lsa = new List[10];//未检测警告-这是不安全的! Object o = lsa; Object[] oa = (Object[]) o; List li = new ArrayList(); li.add(new Integer(3)); oa[1]=li;//正确 String s = lsa[1].get(0);//运行出错,但之前已经被警告 类似地,想创建一个元素类型是类型变量的数组对象的话,将会编译出错。 T[] makeArray(T t){ return new T[100];//错误 } 因为类型变量并不存在于运行时,所以没有办法知道实际的数组类型是什么。 要突破这类限制,我们可以用第 8 部分说到的用类名作为运行时标记的方法。 免费 linux 公开课,,现在报名! 八、 把类名作为运行时的类型标记 JDK1.5 中的一个变化是 java.lang.Class 是泛化的,一个有趣的例子是对 容器外的东西使用泛型。 现在 Class 类有一个类型参数 T,你可能会问,T 代表什么啊?它就代表 Class 对象所表示的类型。 比如,String.class 的类型是 Class,Serializable.class 的 类型是 Class,这可以提高你的反射代码中的类型安全性。 特别地,由于现在 Class 类中的 newInstance()方法返回一个 T 对象,因此 在通过反射创建对象的时候可以得到更精确的类型。 其中一个方法就是显式传入一个 factory 对象,代码如下: interface Factory {T make();} public Collection select(Factory factory, String statement){ Collection result = new ArrayList(); //用 JDBC 运行 SQL 查询 for(/*遍历 JDBC 结果*/){ T item = factory.make(); /*通过 SQL 结果用反射和设置数据项*/ result.add(item); } return result; } 你可以这样调用: select(new Factory(){ public EmpInfo make() { return new EmpInfo(); }} , "selection string"); 或者声明一个 EmpInfoFactory 类来支持 Factory 接口: class EmpInfoFactory implements Factory{ ... public EmpInfo make() { return new EmpInfo();} } 然后这样调用: select(getMyEmpInfoFactory(), "selection string"); 这种解决办法需要下面的其中之一: · 在调用的地方使用详细的匿名工厂类(verbose anonymous factory classes),或者 · 为每个使用的类型声明一个工厂类, 并把工厂实例传递给调用的地方, 这样有点不自然。 使用类名作为一个工厂对象是非常自然的事,这样的话还可以为反射所用。