<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Vincent

    Vicent's blog
    隨筆 - 74, 文章 - 0, 評論 - 5, 引用 - 0
    數據加載中……

    Java 理論和實踐: 了解泛型

    JDK 5.0 中增加的泛型類型,是 Java 語言中類型安全的一次重要改進。但是,對于初次使用泛型類型的用戶來說,泛型的某些方面看起來可能不容易明白,甚至非常奇怪。在本月的“Java 理論和實踐”中,Brian Goetz 分析了束縛第一次使用泛型的用戶的常見陷阱。您可以通過討論論壇與作者和其他讀者分享您對本文的看法。(也可以單擊本文頂端或底端的討論來訪問這個論壇。)

    表面上看起來,無論語法還是應用的環境(比如容器類),泛型類型(或者泛型)都類似于 C++ 中的模板。但是這種相似性僅限于表面,Java 語言中的泛型基本上完全在編譯器中實現,由編譯器執行類型檢查和類型推斷,然后生成普通的非泛型的字節碼。這種實現技術稱為擦除(erasure)(編譯器使用泛型類型信息保證類型安全,然后在生成字節碼之前將其清除),這項技術有一些奇怪,并且有時會帶來一些令人迷惑的后果。雖然范型是 Java 類走向類型安全的一大步,但是在學習使用泛型的過程中幾乎肯定會遇到頭痛(有時候讓人無法忍受)的問題。

    注意:本文假設您對 JDK 5.0 中的范型有基本的了解。

    泛型不是協變的

    雖然將集合看作是數組的抽象會有所幫助,但是數組還有一些集合不具備的特殊性質。Java 語言中的數組是協變的(covariant),也就是說,如果 Integer 擴展了 Number(事實也是如此),那么不僅 IntegerNumber,而且 Integer[] 也是 Number[],在要求 Number[] 的地方完全可以傳遞或者賦予 Integer[]。(更正式地說,如果 NumberInteger 的超類型,那么 Number[] 也是 Integer[] 的超類型)。您也許認為這一原理同樣適用于泛型類型 —— List<Number>List<Integer> 的超類型,那么可以在需要 List<Number> 的地方傳遞 List<Integer>。不幸的是,情況并非如此。

    不允許這樣做有一個很充分的理由:這樣做將破壞要提供的類型安全泛型。如果能夠將 List<Integer> 賦給 List<Number>。那么下面的代碼就允許將非 Integer 的內容放入 List<Integer>

    												
    														List<Integer> li = new ArrayList<Integer>();
    List<Number> ln = li; // illegal
    ln.add(new Float(3.1415));
    
    												
    										

    因為 lnList<Number>,所以向其添加 Float 似乎是完全合法的。但是如果 lnli 的別名,那么這就破壞了蘊含在 li 定義中的類型安全承諾 —— 它是一個整數列表,這就是泛型類型不能協變的原因。

    其他的協變問題

    數組能夠協變而泛型不能協變的另一個后果是,不能實例化泛型類型的數組(new List<String>[3] 是不合法的),除非類型參數是一個未綁定的通配符(new List<?>[3] 是合法的)。讓我們看看如果允許聲明泛型類型數組會造成什么后果:

    												
    														List<String>[] lsa = new List<String>[10]; // illegal
    Object[] oa = lsa;  // OK because List<String> is a subtype of Object
    List<Integer> li = new ArrayList<Integer>();
    li.add(new Integer(3));
    oa[0] = li; 
    String s = lsa[0].get(0); 
    
    												
    										

    最后一行將拋出 ClassCastException,因為這樣將把 List<Integer> 填入本應是 List<String> 的位置。因為數組協變會破壞泛型的類型安全,所以不允許實例化泛型類型的數組(除非類型參數是未綁定的通配符,比如 List<?>)。





    回頁首


    構造延遲

    因為可以擦除功能,所以 List<Integer>List<String> 是同一個類,編譯器在編譯 List<V> 時只生成一個類(和 C++ 不同)。因此,在編譯 List<V> 類時,編譯器不知道 V 所表示的類型,所以它就不能像知道類所表示的具體類型那樣處理 List<V> 類定義中的類型參數(List<V> 中的 V)。

    因為運行時不能區分 List<String>List<Integer>(運行時都是 List),用泛型類型參數標識類型的變量的構造就成了問題。運行時缺乏類型信息,這給泛型容器類和希望創建保護性副本的泛型類提出了難題。

    比如泛型類 Foo

    												
    														class Foo<T> { 
      public void doSomething(T param) { ... }
    }
    
    												
    										

    假設 doSomething() 方法希望復制輸入的 param 參數,會怎么樣呢?沒有多少選擇。您可能希望按以下方式實現 doSomething()

    												
    														public void doSomething(T param) { 
      T copy = new T(param);  // illegal
    }
    
    												
    										

    但是您不能使用類型參數訪問構造函數,因為在編譯的時候還不知道要構造什么類,因此也就不知道使用什么構造函數。使用泛型不能表達“T 必須擁有一個拷貝構造函數(copy constructor)”(甚至一個無參數的構造函數)這類約束,因此不能使用泛型類型參數所表示的類的構造函數。

    clone() 怎么樣呢?假設在 Foo 的定義中,T 擴展了 Cloneable

    												
    														class Foo<T extends Cloneable> { 
      public void doSomething(T param) {
        T copy = (T) param.clone();  // illegal 
      }
    }
    
    												
    										

    不幸的是,仍然不能調用 param.clone()。為什么呢?因為 clone()Object 中是保護訪問的,調用 clone() 必須通過將 clone() 改寫公共訪問的類引用來完成。但是重新聲明 clone() 為 public 并不知道 T,因此克隆也無濟于事。

    構造通配符引用

    因此,不能復制在編譯時根本不知道是什么類的類型引用。那么使用通配符類型怎么樣?假設要創建類型為 Set<?> 的參數的保護性副本。您知道 Set 有一個拷貝構造函數。而且別人可能曾經告訴過您,如果不知道要設置的內容的類型,最好使用 Set<?> 代替原始類型的 Set,因為這種方法引起的未檢查類型轉換警告更少。于是,可以試著這樣寫:

    												
    														class Foo {
      public void doSomething(Set<?> set) {
        Set<?> copy = new HashSet<?>(set);  // illegal
      }
    }
    
    												
    										

    不幸的是,您不能用通配符類型的參數調用泛型構造函數,即使知道存在這樣的構造函數也不行。不過您可以這樣做:

    												
    														class Foo {
      public void doSomething(Set<?> set) {
        Set<?> copy = new HashSet<Object>(set);  
      }
    }
    
    												
    										

    這種構造不那么直觀,但它是類型安全的,而且可以像 new HashSet<?>(set) 那樣工作。

    構造數組

    如何實現 ArrayList<V>?假設類 ArrayList 管理一個 V 數組,您可能希望用 ArrayList<V> 的構造函數創建一個 V 數組:

    												
    														class ArrayList<V> {
      private V[] backingArray;
      public ArrayList() {
        backingArray = new V[DEFAULT_SIZE]; // illegal
      }
    }
    
    												
    										

    但是這段代碼不能工作 —— 不能實例化用類型參數表示的類型數組。編譯器不知道 V 到底表示什么類型,因此不能實例化 V 數組。

    Collections 類通過一種別扭的方法繞過了這個問題,在 Collections 類編譯時會產生類型未檢查轉換的警告。ArrayList 具體實現的構造函數如下:

    												
    														class ArrayList<V> {
      private V[] backingArray;
      public ArrayList() {
        backingArray = (V[]) new Object[DEFAULT_SIZE]; 
      }
    }
    
    												
    										

    為何這些代碼在訪問 backingArray 時沒有產生 ArrayStoreException 呢?無論如何,都不能將 Object 數組賦給 String 數組。因為泛型是通過擦除實現的,backingArray 的類型實際上就是 Object[],因為 Object 代替了 V。這意味著:實際上這個類期望 backingArray 是一個 Object 數組,但是編譯器要進行額外的類型檢查,以確保它包含 V 類型的對象。所以這種方法很奏效,但是非常別扭,因此不值得效仿(甚至連泛型 Collections 框架的作者都這么說,請參閱參考資料)。

    還有一種方法就是聲明 backingArrayObject 數組,并在使用它的各個地方強制將它轉化為 V[]。仍然會看到類型未檢查轉換警告(與上一種方法一樣),但是它使一些未明確的假設更清楚了(比如 backingArray 不應逃避 ArrayList 的實現)。

    其他方法

    最好的辦法是向構造函數傳遞類文字(Foo.class),這樣,該實現就能在運行時知道 T 的值。不采用這種方法的原因在于向后兼容性 —— 新的泛型集合類不能與 Collections 框架以前的版本兼容。

    下面的代碼中 ArrayList 采用了以下方法:

    												
    														public class ArrayList<V> implements List<V> {
      private V[] backingArray;
      private Class<V> elementType;
    
      public ArrayList(Class<V> elementType) {
        this.elementType = elementType;
        backingArray = (V[]) Array.newInstance(elementType, DEFAULT_LENGTH);
      }
    }
    
    												
    										

    但是等一等!仍然有不妥的地方,調用 Array.newInstance() 時會引起未經檢查的類型轉換。為什么呢?同樣是由于向后兼容性。Array.newInstance() 的簽名是:

    												
    														public static Object newInstance(Class<?> componentType, int length)
    
    												
    										

    而不是類型安全的:

    												
    														public static<T> T[] newInstance(Class<T> componentType, int length)
    
    												
    										

    為何 Array 用這種方式進行泛化呢?同樣是為了保持向后兼容。要創建基本類型的數組,如 int[],可以使用適當的包裝器類中的 TYPE 字段調用 Array.newInstance()(對于 int,可以傳遞 Integer.TYPE 作為類文字)。用 Class<T> 參數而不是 Class<?> 泛化 Array.newInstance(),對于引用類型有更好的類型安全,但是就不能使用 Array.newInstance() 創建基本類型數組的實例了。也許將來會為引用類型提供新的 newInstance() 版本,這樣就兩者兼顧了。

    在這里可以看到一種模式 —— 與泛型有關的很多問題或者折衷并非來自泛型本身,而是保持和已有代碼兼容的要求帶來的副作用。





    回頁首


    泛化已有的類

    在轉化現有的庫類來使用泛型方面沒有多少技巧,但與平常的情況相同,向后兼容性不會憑空而來。我已經討論了兩個例子,其中向后兼容性限制了類庫的泛化。

    另一種不同的泛化方法可能不存在向后兼容問題,這就是 Collections.toArray(Object[])。傳入 toArray() 的數組有兩個目的 —— 如果集合足夠小,那么可以將其內容直接放在提供的數組中。否則,利用反射(reflection)創建相同類型的新數組來接受結果。如果從頭開始重寫 Collections 框架,那么很可能傳遞給 Collections.toArray() 的參數不是一個數組,而是一個類文字:

    												
    														interface Collection<E> { 
      public T[] toArray(Class<T super E> elementClass);
    }
    
    												
    										

    因為 Collections 框架作為良好類設計的例子被廣泛效仿,但是它的設計受到向后兼容性約束,所以這些地方值得您注意,不要盲目效仿。

    首先,常常被混淆的泛型 Collections API 的一個重要方面是 containsAll()removeAll()retainAll() 的簽名。您可能認為 remove()removeAll() 的簽名應該是:

    												
    														interface Collection<E> { 
      public boolean remove(E e);  // not really
      public void removeAll(Collection<? extends E> c);  // not really
    }
    
    												
    										

    但實際上卻是:

    												
    														interface Collection<E> { 
      public boolean remove(Object o);  
      public void removeAll(Collection<?> c);
    }
    
    												
    										

    為什么呢?答案同樣是因為向后兼容性。x.remove(o) 的接口表明“如果 o 包含在 x 中,則刪除它,否則什么也不做。”如果 x 是一個泛型集合,那么 o 不一定與 x 的類型參數兼容。如果 removeAll() 被泛化為只有類型兼容時才能調用(Collection<? extends E>),那么在泛化之前,合法的代碼序列就會變得不合法,比如:

    												
    														// a collection of Integers
    Collection c = new HashSet();
    // a collection of Objects
    Collection r = new HashSet();
    c.removeAll(r);
    
    												
    										

    如果上述片段用直觀的方法泛化(將 c 設為 Collection<Integer>r 設為 Collection<Object>),如果 removeAll() 的簽名要求其參數為 Collection<? extends E> 而不是 no-op,那么就無法編譯上面的代碼。泛型類庫的一個主要目標就是不打破或者改變已有代碼的語義,因此,必須用比從頭重新設計泛型所使用類型約束更弱的類型約束來定義 remove()removeAll()retainAll()containsAll()

    在泛型之前設計的類可能阻礙了“顯然的”泛型化方法。這種情況下就要像上例這樣進行折衷,但是如果從頭設計新的泛型類,理解 Java 類庫中的哪些東西是向后兼容的結果很有意義,這樣可以避免不適當的模仿。





    回頁首


    擦除的實現

    因為泛型基本上都是在 Java 編譯器中而不是運行庫中實現的,所以在生成字節碼的時候,差不多所有關于泛型類型的類型信息都被“擦掉”了。換句話說,編譯器生成的代碼與您手工編寫的不用泛型、檢查程序的類型安全后進行強制類型轉換所得到的代碼基本相同。與 C++ 不同,List<Integer>List<String> 是同一個類(雖然是不同的類型但都是 List<?> 的子類型,與以前的版本相比,在 JDK 5.0 中這是一個更重要的區別)。

    擦除意味著一個類不能同時實現 Comparable<String>Comparable<Number>,因為事實上兩者都在同一個接口中,指定同一個 compareTo() 方法。聲明 DecimalString 類以便與 StringNumber 比較似乎是明智的,但對于 Java 編譯器來說,這相當于對同一個方法進行了兩次聲明:

    												
    														public class DecimalString implements Comparable<Number>, Comparable<String> { ... } // nope
    
    												
    										

    擦除的另一個后果是,對泛型類型參數是用強制類型轉換或者 instanceof 毫無意義。下面的代碼完全不會改善代碼的類型安全性:

    												
    														public <T> T naiveCast(T t, Object o) { return (T) o; }
    
    												
    										

    編譯器僅僅發出一個類型未檢查轉換警告,因為它不知道這種轉換是否安全。naiveCast() 方法實際上根本不作任何轉換,T 直接被替換為 Object,與期望的相反,傳入的對象被強制轉換為 Object

    擦除也是造成上述構造問題的原因,即不能創建泛型類型的對象,因為編譯器不知道要調用什么構造函數。如果泛型類需要構造用泛型類型參數來指定類型的對象,那么構造函數應該接受類文字(Foo.class)并將它們保存起來,以便通過反射創建實例。

    posted on 2006-08-24 17:31 Binary 閱讀(265) 評論(0)  編輯  收藏 所屬分類: j2se

    主站蜘蛛池模板: 亚洲AV日韩AV永久无码免下载| 亚洲人成网站色在线观看| 亚洲一区二区影视| 1000部啪啪毛片免费看| 久久精品蜜芽亚洲国产AV| 99精品免费观看| 亚洲电影免费观看| 999国内精品永久免费观看| 亚洲欧洲日产国码在线观看| 18禁美女裸体免费网站| 亚洲av无码电影网| 国产在线不卡免费播放| 免费人人潮人人爽一区二区| 亚洲一级片免费看| 国产午夜免费高清久久影院| 亚洲精品国产免费| 妞干网免费视频在线观看| 噜噜噜亚洲色成人网站| 亚洲精品无码Av人在线观看国产| 亚洲国产综合人成综合网站00| 国产精品亚洲一区二区三区在线观看| 中国videos性高清免费| 久久精品国产99精品国产亚洲性色| 久久亚洲精品国产精品婷婷| 免费毛片在线播放| 一个人看www免费高清字幕| 久久91亚洲精品中文字幕| 免费可以看黄的视频s色| 亚洲色成人四虎在线观看| 亚洲精品国产精品乱码不卞| 久久精品免费电影| 亚洲 欧洲 日韩 综合在线| 亚洲v国产v天堂a无码久久| 久久成人免费大片| 亚洲精品国产suv一区88| 亚洲男同帅GAY片在线观看| 免费看成人AA片无码视频羞羞网| 久久精品国产亚洲av麻豆色欲 | 亚洲av成人综合网| 女人张开腿等男人桶免费视频| 亚洲人成在线播放|