久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

淺析Java泛型

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

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

泛型不是協(xié)變的

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

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

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

因?yàn)?nbsp;ln 是 List<Number>,所以向其添加 Float 似乎是完全合法的,。但是如果 ln 是 li 的別名,,那么這就破壞了蘊(yùn)含在 li 定義中的類型安全承諾 —— 它是一個整數(shù)列表,這就是泛型類型不能協(xié)變的原因,。 

其他的協(xié)變問題

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

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

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





構(gòu)造延遲

因?yàn)榭梢圆脸δ?,所?nbsp;List<Integer> 和 List<String> 是同一個類,,編譯器在編譯 List<V> 時只生成一個類(和 C++ 不同)。因此,,在編譯 List<V> 類時,,編譯器不知道 V 所表示的類型,所以它就不能像知道類所表示的具體類型那樣處理 List<V> 類定義中的類型參數(shù)(List<V> 中的 V),。 

因?yàn)檫\(yùn)行時不能區(qū)分 List<String> 和 List<Integer>(運(yùn)行時都是 List),用泛型類型參數(shù)標(biāo)識類型的變量的構(gòu)造就成了問題,。運(yùn)行時缺乏類型信息,,這給泛型容器類和希望創(chuàng)建保護(hù)性副本的泛型類提出了難題,。 

比如泛型類 Foo: 

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

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

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

但是您不能使用類型參數(shù)訪問構(gòu)造函數(shù),,因?yàn)樵诰幾g的時候還不知道要構(gòu)造什么類,,因此也就不知道使用什么構(gòu)造函數(shù),。使用泛型不能表達(dá)“T 必須擁有一個拷貝構(gòu)造函數(shù)(copy constructor)”(甚至一個無參數(shù)的構(gòu)造函數(shù))這類約束,,因此不能使用泛型類型參數(shù)所表示的類的構(gòu)造函數(shù)。 

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

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

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

構(gòu)造通配符引用

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

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

不幸的是,,您不能用通配符類型的參數(shù)調(diào)用泛型構(gòu)造函數(shù),即使知道存在這樣的構(gòu)造函數(shù)也不行,。不過您可以這樣做: 

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

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

構(gòu)造數(shù)組

如何實(shí)現(xiàn) ArrayList<V>,?假設(shè)類 ArrayList 管理一個 V 數(shù)組,,您可能希望用 ArrayList<V> 的構(gòu)造函數(shù)創(chuàng)建一個 V 數(shù)組: 

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

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

Collections 類通過一種別扭的方法繞過了這個問題,,在 Collections 類編譯時會產(chǎn)生類型未檢查轉(zhuǎn)換的警告,。ArrayList 具體實(shí)現(xiàn)的構(gòu)造函數(shù)如下: 

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

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

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

其他方法

最好的辦法是向構(gòu)造函數(shù)傳遞類文字(Foo.class),這樣,,該實(shí)現(xiàn)就能在運(yùn)行時知道 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);  }}

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

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

而不是類型安全的: 

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

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

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





泛化已有的類

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

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

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

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

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

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

但實(shí)際上卻是: 

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

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

// a collection of IntegersCollection c = new HashSet();// a collection of ObjectsCollection r = new HashSet();c.removeAll(r);

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

在泛型之前設(shè)計(jì)的類可能阻礙了“顯然的”泛型化方法,。這種情況下就要像上例這樣進(jìn)行折衷,但是如果從頭設(shè)計(jì)新的泛型類,,理解 Java 類庫中的哪些東西是向后兼容的結(jié)果很有意義,,這樣可以避免不適當(dāng)?shù)哪7隆?nbsp;





擦除的實(shí)現(xiàn)

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

擦除意味著一個類不能同時實(shí)現(xiàn) Comparable<String> 和 Comparable<Number>,,因?yàn)槭聦?shí)上兩者都在同一個接口中,,指定同一個 compareTo() 方法。聲明 DecimalString 類以便與 String 與 Number 比較似乎是明智的,,但對于 Java 編譯器來說,這相當(dāng)于對同一個方法進(jìn)行了兩次聲明: 

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

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

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

編譯器僅僅發(fā)出一個類型未檢查轉(zhuǎn)換警告,,因?yàn)樗恢肋@種轉(zhuǎn)換是否安全,。naiveCast() 方法實(shí)際上根本不作任何轉(zhuǎn)換,,T 直接被替換為 Object,與期望的相反,,傳入的對象被強(qiáng)制轉(zhuǎn)換為 Object,。 

擦除也是造成上述構(gòu)造問題的原因,即不能創(chuàng)建泛型類型的對象,,因?yàn)榫幾g器不知道要調(diào)用什么構(gòu)造函數(shù)。如果泛型類需要構(gòu)造用泛型類型參數(shù)來指定類型的對象,,那么構(gòu)造函數(shù)應(yīng)該接受類文字(Foo.class)并將它們保存起來,,以便通過反射創(chuàng)建實(shí)例。 



    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點(diǎn)擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多