Generics Types 泛型學習筆記<三>

Generics Types 泛型學習筆記<三>

作者:冰雲
時間:
2004-02-29
聯繫:
icecloud(AT)sina.com
Bloghttp://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>TObject類型,返回的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, youll 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中很重要的部分就是genericsjava generics的誕生大概與.net的逼迫有關係,之前的多個版本從未看到有如此大的變化。

java這種強類型的語言中,type casting是很難控制的一個事情,編譯器可以在編譯時解決一部分,在運行時刻遇到這種問題,是程序潛在的隱患。reflection技術是解決應用的靈活配置的一個重要手段,但只能在運行時刻才能知道究竟是哪個類,可能會有casting的問題。

C++中的模板技術的目的是提供快捷、易於使用的工具箱,STLc++編程必須掌握的技術。java generics看起來與c++模板很象,但通過這篇文章我們知道,java generics更着重在類型檢查上,可以去掉看起來很難猜測的強制類型轉換,大概只有程序編寫者自己最清楚到底這裏是什麼類型。使用java generics,程序可能會更好讀一些,也降低了運行時刻的風險,但這裏提到的幾個概念和技術,卻也並不易於理解(文中也有幾處筆誤)。theserverside.com對於是否需要這種技術有爭論,不管怎樣,又給我們提供了一個手段,需要的時候是用的上的。

 

    花了3天看完的,花了1個星期才寫完。真是費力啊。感謝各位一直捧場支持。有什麼問題歡迎來我

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章