分类: Java
2007-05-20 11:18:54
Java1.5泛型指南中文版(Java1.5 Generic Tutorial)
英文版pdf下载链接:
译者:
目 录
(第五部分)
下面的代码打印的结果是什么?
List
List
System.out.println(l1.getClass() == l2.getClass());
或许你会说false,但是那你就错了。它打印出true。因为所有的泛型类型在运行时有同样的类(class),而不管他们的实际类型参数。
事实上,泛型之所以为泛型就是因为它对所有其可能的类型参数,它有同样的行为;同样的类可以被当作许多不同的类型。
作为一个结果,类的静态变量和方法也在所有的实例间共享。这就是为什么在静态方法或静态初始化代码中或者在静态变量的声明和初始化时使用类型参数申明是不合法的原因。
(原文:As consequence, the static variables and methods of a class are also shared among all the instances. That is why it is illegal to refer to the type parameters of a type declaration in a static method or initializer, or in the declaration or initializer of a static variable.)
泛型类被所有其实例(instances)共享的另一个暗示是检查一个实例是不是一个特定类型的泛型类是没有意义的。
Collection cs = new ArrayList
if (cs instanceof Collection
类似的,如下的类型转换
Collection
得到一个unchecked warning,因为运行时环境不会为你作这样的检查。
对类型变量也是一样:
return (T) o; // unchecked warning
}
类型参数在运行时并不存在。这意味着它们不会添加任何的时间或者空间上的负担,这很好。不幸的是,这也意味着你不能依靠他们进行类型转换。
数组对象的组成类型不能是一个类型变量或者类型参数,除非它是无上限的通配符类型。你可以声明元素类型是一个类型参数或者参数化类型的数组类型,但不是数组对象(译注:得不到对象,只能声明)。
(原文:The component type of an array object may not be a type variable or a parameterized type, unless it is an (unbounded) wildcard type.You can declare array types whose element type is a type variable or a parameterized type, but not array objects.)
这很烦人,但是确实时这样。为了避免下面的情况,必须有这样的限制:
List
Object o = lsa;
Object[] oa = (Object[]) o;
List
li.add(new Integer(3));
oa[1] = li; // unsound, but passes run time store check
String s = lsa[1].get(0); // run-time error - ClassCastException
如果参数化类型可以是数组,那么意味着上面的例子可以没有任何unchecked warnings的通过编译,但是在运行时失败。我们把类型安全(type-safety)作为泛型首要的设计目标。特别的,java语言被设计为保证:如果你的整个程序没有unchecked warnings的使用javac –source1.5通过编译,那么它是类型安全的(原文: if your entire application has been compiled without unchecked warnings using javac -source 1.5, it is type safe)。
然和,你仍然可以使用通配符数组。上面的代码有两种变化。第一种改变放弃使用数组对象和元素类型参数化的数组类型。结果是,我们不得不显式的进行类型转换来从数组中获得一个String。
List>[] lsa = new List>[10]; // ok, array of unbounded wildcard type
Object o = lsa;
Object[] oa = (Object[]) o;
List
li.add(new Integer(3));
oa[1] = li; // correct
String s = (String) lsa[1].get(0); // run time error, but cast is explicit
在下面的变体中,我们避免了产生一个元素类型是参数化的数组对象,但是使用了元素类型参数化的类型。(译注:意思如下面的第一行代码所示,声明一个泛型化的数组,但是new的时候使用的是raw type,原文中是 new ArrayList>(10),那是错的,已经修正为new ArrayList(10);)这是合法的,但是产生一个unchecked warning。实际上,这个代码是不安全的,最后产生一个错误。
List
Object o = lsa;
Object[] oa = (Object[]) o;
List
li.add(new Integer(3));
oa[1] = li; // correct
String s = lsa[1].get(0); // run time error, but we were warned
类似的,创建一个元素类型是一个类型变量的数组对象导致一个编译时错误:
return new T[100]; // error
}
因为类型变量在运行时并不存在,所以没有办法决定实际类型是什么。
解决这些限制的办法是使用字面的类作为运行时类型标志(原文:use class literals as run time type tokens),见第8部分。
JDK1.5中一个变化是类 java.lang.Class是泛型化的。这是把泛型作为容器类之外的一个很有意思的例子(using genericity for something other than a container class)。
现在,Class有一个类型参数T, 你很可能会问,T 代表什么?
它代表Class对象代表的类型。比如说,String.class类型代表 Class
特别的,因为 Class的 newInstance() 方法现在返回一个T, 你可以在使用反射创建对象时得到更精确的类型。
比如说,假定你要写一个工具方法来进行一个数据库查询,给定一个SQL语句,并返回一个数据库中符合查询条件的对象集合(collection)。
一个方法时显式的传递一个工厂对象,像下面的代码:
interface Factory
public T[] make();
}
public
Collection
/* run sql query using jdbc */
for (int i=0;i<10;i++/* iterate over jdbc results */ ) {
T item = factory.make();
/* use reflection and set all of item’s fields from sql results */
result.add(item);
}
return result;
}
你可以这样调用:
select(new Factory<EmpInfo>(){
public EmpInfo make() {
return new EmpInfo();
}
} , ”selection string”);
也可以声明一个类 EmpInfoFactory 来支持接口 Factory:
class EmpInfoFactory implements Factory<EmpInfo> { ...
public EmpInfo make() { return new EmpInfo();}
}
然后调用:
select(getMyEmpInfoFactory(), "selection string");
这个解决方案的缺点是它需要下面的二者之一:
l 调用处那冗长的匿名工厂类,或
l 为每个要使用的类型声明一个工厂类并传递其对象给调用的地方
这很不自然。
使用class literal作为工厂对象是非常自然的,它可以被发射使用。没有泛型的代码可能是:
Collection emps = sqlUtility.select(EmpInfo.class, ”select * from emps”); ...
public static Collection select(Class c, String sqlStatement) {
Collection result = new ArrayList();
/* run sql query using jdbc */
for ( /* iterate over jdbc results */ ) {
Object item = c.newInstance();
/* use reflection and set all of item’s fields from sql results */
result.add(item);
}
return result;
}
但是这不能给我们返回一个我们要的精确类型的集合。现在Class是泛型的,我们可以写:
Collection<EmpInfo> emps=sqlUtility.select(EmpInfo.class, ”select * from emps”); ...
public static <T> Collection<T> select(Class<T>c, String sqlStatement) {
Collection<T> result = new ArrayList<T>();
/* run sql query using jdbc */
for ( /* iterate over jdbc results */ ) {
T item = c.newInstance();
/* use reflection and set all of item’s fields from sql results */
result.add(item);
}
return result;
}
来通过一种类型安全的方式得到我们要的集合。
这项技术是一个非常有用的技巧,它已成为一个在处理注释(annotations)的新API中被广泛使用的习惯用法。