Generics Types 泛型学习笔记<三>

类别:Java 点击:0 评论:0 推荐:

Generics Types 泛型学习笔记<三>

作者:冰云
时间: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");

   

    public static <T> Collection<T> select(Class<T> c, String sqlStatement) {

       Collection<T> result = new ArrayList<T>();

       // run sql query using JDBC

       for (/* 遍历ResultSet */){

           T item = c.newInstance();

           /* set all item's fields using reflection*/

           result.add(item);

       }

       return result;

    }

 

 

上面的这个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 通配符更多的作用

 

来看下面的例子

 

   

    public interface Sink<T>{

       flush(T t);

    }

   

    public static<T> T writeAll (Collection<T> coll, Sink<T> sink){

       T last ;

       for(T t : coll){

           last = t;

           snk.flush(last);

       }

       return last;

    }

   

    Sink<Object> s;

    Collection<String> cs;

    String str = writeAll(cs,s); // 非法调用

 

 

由于调用时,编译器无法确定<T>是什么类型,String还是Object,所以会错误。根据前面的知识,可以使用通配符类型作如下修改:

 

   

    public static<T> T writeAll (Collection<? extends T> coll, Sink<T> sink)

 

    Sink<Object> s;

    Collection<String> cs;

    String str = writeAll(cs,s); // 调用合法,但返回值是Object

 

 

<? extends T>,根据Sink<Object>,T是Object类型,返回的T不能安全的给String类型。

这时,就引入了另一种通配符类型:下限通配符(lower bound):<? super T>。

 

   

    public static<T> T writeAll (Collection<T> coll, Sink<? super T> sink)

    String str = 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中的元素顺序。并在构造函数中接受。

 

   

        public interface Comparator<T>{

       int compareTo(T fst, T snd);

    }

   

    TreeSet(Comparator<E> c);

 

 

我们提供Comparator<String>可以正常的比较,然而,如果提供Comparator<Object>,也应该正常的工作。因此,TreeSet构造函数应该修改为:

 

   

    TreeSet(Comparator<? super E> c);

 

 

同样,在Collection中的max方法,返回一个最大值。这些元素必须实现Comparable接口。Collection.max(Comparable<Object>)应该能够工作。而对于 class Foo implements Comparable<Object>,如果提供Comparable<Foo>,应该也能够正常运行。因此,这里也引入下限通配符。

 

   

    // T必须是实现Comparable的

    public static <T extends Comparable<T>>

           T max(Collection<T> coll);

   

    // T还应该能够接受一个实际的类型,所以修改为

    public static <T extends Comparable<? super T>

           T max(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 = new HashSet<String>();

    ...

    public static <T> void addToSet(Set<T> s , T t);

    addToSet(unknownSet, "abc"); // 非法调用

 

 

由于unknownSet是未知类型,因此不能接受任何实际的类型。如String。考虑下面的代码:

 

   

    class Collections{

       <T> public static Set<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:

 

 

   

    public interface Collection {

       public boolean containsAll(Collection c);

       public boolean addAll(Collection c);

    }  

    public interface Collection<E> {

       public boolean containsAll(Collection<E> c);

       public boolean addAll(Collection<E> c);

    }

 

这是类型安全的,但是并不兼容旧代码。因为范型代码只能接受E类型的Collectioni。

    必须考虑到,addAll能够添加任何E的子类型。containsAll也要能够接受不同的类型等等。

 

    第9节提到了max方法。

 

   

    public static <T extends Comparable<? super T>

           T max(Collection<T> coll);

 

 

这存在一个问题,就是和旧的代码无法吻合:

 

   

    public static Comparable max(Collection coll); //新

    public static Object max(Collection coll); // 旧

 

 

    通过显式声明一个超类,可以强迫他们一致。并且,我们知道,max仅从他的输入Colelction读取,所以适用于任何T的子类,修改如下:

 

   

    public static <T extends Object & Comparable<? super T>

           T max(Collection<? extends T> 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)子类方法的返回值。旧代码如下:

 

   

    public class Foo {

       public Foo create) {} // 工厂方法

    }

    public class Bar extends Foo {

       public Foo create() {} // 实际上建立的Bar

    }

 

 

考虑返回值协变的优点:

 

   

    public class Foo {

       public Foo create) {} // 工厂方法

    }

    public class Bar extends Foo {

       public Bar create() {} // 实际上建立的Bar

    }

 

 

Note 13: 这种特性并不被JVM直接支持,而是被编译器所支持。

原文:The JVM does not directly support overriding of methods with different return types. This feature is supported by the compiler.

 

    在此我引用我朋友udoo的一段话作为总结: http://udoo.51.net

Java 1.5中很重要的部分就是generics。java generics的诞生大概与.net的逼迫有关系,之前的多个版本从未看到有如此大的变化。

在java这种强类型的语言中,type casting是很难控制的一个事情,编译器可以在编译时解决一部分,在运行时刻遇到这种问题,是程序潜在的隐患。reflection技术是解决应用的灵活配置的一个重要手段,但只能在运行时刻才能知道究竟是哪个类,可能会有casting的问题。

C++中的模板技术的目的是提供快捷、易于使用的工具箱,STL是c++编程必须掌握的技术。java generics看起来与c++模板很象,但通过这篇文章我们知道,java generics更着重在类型检查上,可以去掉看起来很难猜测的强制类型转换,大概只有程序编写者自己最清楚到底这里是什么类型。使用java generics,程序可能会更好读一些,也降低了运行时刻的风险,但这里提到的几个概念和技术,却也并不易于理解(文中也有几处笔误)。theserverside.com对于是否需要这种技术有争论,不管怎样,又给我们提供了一个手段,需要的时候是用的上的。

 

    花了3天看完的,花了1个星期才写完。真是费力啊。感谢各位一直捧场支持。有什么问题欢迎来我blog讨论.

    


版权声明:
本文由冰云完成,首发于CSDN,作者保留中文版权。
未经许可,不得使用于任何商业用途。
欢迎转载,但请保持文章及版权声明完整。
如需联络请发邮件:icecloud(AT)sina.com


本文地址:http://com.8s8s.com/it/it16802.htm