作者:冰云
时间:2004-02-29
联系:icecloud(AT)sina.com
Blog:http://icecloud.51.net
真不好意思,这么久才提交上来,有些事情耽搁了。
8 类文字?(Class Literals)作为运行时类型记号(Type Tokens)
在1.5中,java.lang.Class是泛型的,即有一个类型参数T。如String.class,的类型就是Class<String>。这样的好处是,当你使用 reflect构造一个类的时候,可以得到更精确的类别而不是泛泛的Object。
Collection<EmployeeInfo>emps= sqlUtility.select(EmployeeInfo.class,"select name,id from emps"); publicstatic<T>Collection<T>select(Class<T>c,StringsqlStatement){ Collection<T>result=newArrayList<T>(); // run sql query using JDBC for(/* 遍历ResultSet */){ Titem=c.newInstance(); /* set all item's fields using reflection*/ result.add(item); } returnresult; } |
上面的这个select方法适合于所有的类。这样就免去了类型转换。
Note 10:Class作为运行时的记号是个很有用的技巧。在新的annotation API中广泛的应用了这种技术。
原文:This technique of using class literals as run time type tokens is a very useful trick to know. It is used extensively in the new APIs for manipulating annotations.
9 通配符更多的作用
来看下面的例子
publicinterfaceSink<T>{ flush(Tt); } publicstatic<T>TwriteAll (Collection<T>coll,Sink<T>sink){ Tlast; for(Tt:coll){ last=t; snk.flush(last); } returnlast; } Sink<Object>s; Collection<String>cs; Stringstr=writeAll(cs,s);// 非法调用 |
由于调用时,编译器无法确定<T>是什么类型,String还是Object,所以会错误。根据前面的知识,可以使用通配符类型作如下修改:
publicstatic<T>TwriteAll (Collection<?extendsT>coll,Sink<T>sink) Sink<Object>s; Collection<String>cs; Stringstr=writeAll(cs,s);// 调用合法,但返回值是Object |
<?extendsT>,根据Sink<Object>,T是Object类型,返回的T不能安全的给String类型。
这时,就引入了另一种通配符类型:下限通配符(lower bound):<?superT>。
publicstatic<T>TwriteAll (Collection<T>coll,Sink<?superT>sink) Stringstr=writeAll(cs,s);// OK |
Note 11:下限通配符? super T表示一个未知类型是T的超类型,就象? extends T表示未知类型是T的子类型一样。
原文:The solution is to use a form of lower bounded wildcard. The syntax ? super T denotes an unkmown type that is supertype of T. it is the twin of the bounded wildcard we use ? extends T to denote an unknown type that is a subtype of T.
文中后又举了一个例子,是关于Collection中,元素比较大小的方法。例如在TreeSet中,应该提供一个Comparator来比较TreeSet中的元素顺序。并在构造函数中接受。
publicinterfaceComparator<T>{ intcompareTo(Tfst,Tsnd); } TreeSet(Comparator<E>c); |
我们提供Comparator<String>可以正常的比较,然而,如果提供Comparator<Object>,也应该正常的工作。因此,TreeSet构造函数应该修改为:
TreeSet(Comparator<? super E>c); |
同样,在Collection中的max方法,返回一个最大值。这些元素必须实现Comparable接口。Collection.max(Comparable<Object>)应该能够工作。而对于classFooimplementsComparable<Object>,如果提供Comparable<Foo>,应该也能够正常运行。因此,这里也引入下限通配符。
// T必须是实现Comparable的 publicstatic<TextendsComparable<T>> Tmax(Collection<T>coll); // T还应该能够接受一个实际的类型,所以修改为 publicstatic<TextendsComparable<?superT> Tmax(Collection<T>coll); |
那么,如何正确的使用通配符呢?
Note 12:一般的,如果你的API仅仅使用类型参数T作为参数,它应该利用下限通配符;相反,如果你的API返回T,你应该用上限通配符给你的客户端更多地便利。
原文:In general if you have an API that only uses a type parameter T as an argument, its uses should take advantage of lower bounded wildcard. Conversely, if the API only returns T, you’ll give your clients more flexibility by using upper bounded wildcards.
通配符捕获,Wildcard capture
下面的例子应该已经很明确的不能执行:
Set<?>unknownSet=newHashSet<String>(); ... publicstatic<T>voidaddToSet(Set<T>s,Tt); addToSet(unknownSet,"abc");// 非法调用 |
由于unknownSet是未知类型,因此不能接受任何实际的类型。如String。考虑下面的代码:
classCollections{ <T>publicstaticSet<T>unmodifiableSet(Set<T>set); } Set<?>s=Collections.unmodifiableSet(unknownSet);// works! |
看起来这段代码不应该被允许,但是它是合法的。这是因为通配符捕获原则。
Note 13:这种情形出现的很频繁,因而有一个特殊的规则允许这样的代码出现,这被证明是安全的:通配符捕获,允许编译器判断未知通配符类型作为类型参数的范型方法。通配符捕获仅仅允许那些在方法参数列表中出现一次的类型参数。
原文:Because this situation arises relatively frequently, there is a special rule that allows such code under very specific circumstances in which the code can be proven to be safe. This rule, known as wildcard capture, allows the compiler to infer the unknown type of a wildcard as a type argument to a generic method. Wildcard capture is only allowed if the type parameter being inferred only appears once in the methods argument list.
10 转换旧代码到范型
这称之为范型化(generifying)。如果要转换旧代码到范型,请仔细的考虑如何修改。如java.util.Collection:
publicinterfaceCollection{ publicbooleancontainsAll(Collectionc); publicbooleanaddAll(Collectionc); } publicinterfaceCollection<E>{ publicbooleancontainsAll(Collection<E>c); publicbooleanaddAll(Collection<E>c); } |
这是类型安全的,但是并不兼容旧代码。因为范型代码只能接受E类型的Collectioni。
必须考虑到,addAll能够添加任何E的子类型。containsAll也要能够接受不同的类型等等。
第9节提到了max方法。
publicstatic<TextendsComparable<?superT> Tmax(Collection<T>coll); |
这存在一个问题,就是和旧的代码无法吻合:
publicstaticComparablemax(Collectioncoll);//新 publicstaticObjectmax(Collectioncoll);// 旧 |
通过显式声明一个超类,可以强迫他们一致。并且,我们知道,max仅从他的输入Colelction读取,所以适用于任何T的子类,修改如下:
publicstatic<TextendsObject&Comparable<?superT> Tmax(Collection<?extendsT>coll); |
这种情况比较少见,但是如果设计一个library,就应该准备认真考虑转换他们。
Note 14:转换一套既有的API时,你必须确定范型API不会过度的限制,并且继续支持原来的API。。
原文:When converting existing APIs, you should think carefully to make certain that the generic API is not unduly restrictive and continue to support the original contract of the API.
另一种需要当心的问题是:返回值协变(covariant returns)。即改进(refine)子类方法的返回值。旧代码如下:
publicclassFoo{ publicFoocreate){} // 工厂方法 } publicclassBarextendsFoo{ publicFoocreate(){} // 实际上建立的Bar } |
考虑返回值协变的优点:
publicclassFoo{ publicFoocreate){} // 工厂方法 } publicclassBarextendsFoo{ publicBarcreate()
|