睫晉姬

          JDK 5.0中的泛型類型學(xué)習(xí)

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

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

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

            泛型不是協(xié)變的

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

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

            List<Integer> li = new ArrayList<Integer nike jordan 2010>();

            List<Number> ln = li; // illegal

            ln.add(new Float(3.1415));

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

            其他的協(xié)變問(wèn)題

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

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

            構(gòu)造延遲

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

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

            比如泛型類 Foo:

            class Foo<T> {

            public void doSomething(T param) { ... }

            }

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

            泛化已有的類

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

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

            interface Collection<E> {

            public T[] toArray(Class<T super E> elementClass);

            }

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

            首先,常常被混淆的泛型 Collections API 的一個(gè)重要方面是 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)橄蚝蠹嫒菪浴.remove(o) 的接口表明“如果 o 包含在 x 中,則刪除它,否則什么也不做。”如果 x 是一個(gè)泛型集合,那么 o 不一定與 x 的類型參數(shù)兼容。如果 removeAll() 被泛化為只有類型兼容時(shí)才能調(diào)用(Collection<? extends E>),那么在泛化之前,合法的代碼序列就會(huì)變得不合法,比如:

            // a collection of Integers

            Collection c = new HashSet();

            // a collection of Objects

            Collection r = new HashSet();

            c.removeAll(r);

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

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

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

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

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

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

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

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

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

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

            結(jié)束語(yǔ)

            泛型是 Java 語(yǔ)言走向類型安全的一大步,但是泛型設(shè)施的設(shè)計(jì)和類庫(kù)的泛化并非未經(jīng)過(guò)妥協(xié)。擴(kuò)展虛擬機(jī)指令集來(lái)支持泛型被認(rèn)為是無(wú)法接受的,因?yàn)檫@會(huì)為 Java 廠商升級(jí)其 JVM 造成難以逾越的障礙。因此采用了可以完全在編譯器中實(shí)現(xiàn)的擦除方法。類似地,在泛型 Java 類庫(kù)時(shí),保持向后兼容也為類庫(kù)的泛化方式設(shè)置了很多限制,產(chǎn)生了一些混亂的、令人沮喪的結(jié)構(gòu)(如 Array.newInstance())。這并非泛型本身的問(wèn)題,而是與語(yǔ)言的演化與兼容有關(guān)。但這些也使得泛型學(xué)習(xí)和應(yīng)用起來(lái)更讓人迷惑,更加困難。

          posted on 2010-07-29 15:23 睫晉姬 閱讀(218) 評(píng)論(0)  編輯  收藏


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 麻阳| 巴林右旗| 恩平市| 乌拉特中旗| 大宁县| 湘潭县| 卢龙县| 长垣县| 钟山县| 安达市| 无锡市| 赣州市| 乐陵市| 大姚县| 惠水县| 若尔盖县| 甘孜县| 丹棱县| 洪泽县| 开原市| 廊坊市| 连南| 宜宾市| 岱山县| 育儿| 昌宁县| 山东省| 福贡县| 鄂伦春自治旗| 瑞金市| 九寨沟县| 洪泽县| 汝南县| 巧家县| 七台河市| 宁国市| 南阳市| 扶沟县| 茶陵县| 年辖:市辖区| 枞阳县|