亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Java 理論與實(shí)踐: 使用通配符簡化泛型使用

系統(tǒng) 1754 0

?

Java 理論與實(shí)踐: 使用通配符簡化泛型使用

理解通配符捕獲
英文原文

級別: 高級

Brian Goetz ( brian.goetz@sun.com ), 高級工程師, Sun Microsystems

2008 年 5 月 26 日

通配符是 Java? 語言中最復(fù)雜的泛型之一,特別是圍繞 捕獲通配符 的處理和令人困惑的錯(cuò)誤消息。在這一期的 Java 理論與實(shí)踐 中,資深 Java 開發(fā)人員 Brian Goetz 解釋了一些由 javac 生成的怪異錯(cuò)誤消息并提供了一些簡化泛型使用的技巧和解決方法。

自從泛型被添加到 JDK 5 語言以來,它一直都是一個(gè)頗具爭議的話題。一部分人認(rèn)為泛型簡化了編程,擴(kuò)展了類型系統(tǒng)從而使編譯器能夠檢驗(yàn)類型安全;另外一些人認(rèn)為泛型添加了很多不必要的復(fù)雜性。對于泛型我們都經(jīng)歷過一些痛苦的回憶,但毫無疑問通配符是最棘手的部分。

通配符基本介紹

泛型是一種表示類或方法行為對于未知類型的類型約束的方法,比如 “不管這個(gè)方法的參數(shù) x y 是哪種類型,它們必須是相同的類型”,“必須為這些方法提供同一類型的參數(shù)” 或者 “ foo() 的返回值和 bar() 的參數(shù)是同一類型的”。

通配符 — 使用一個(gè)奇怪的問號表示類型參數(shù) — 是一種表示未知類型的類型約束的方法。通配符并不包含在最初的泛型設(shè)計(jì)中(起源于 Generic Java(GJ)項(xiàng)目),從形成 JSR 14 到發(fā)布其最終版本之間的五年多時(shí)間內(nèi)完成設(shè)計(jì)過程并被添加到了泛型中。

通配符在類型系統(tǒng)中具有重要的意義,它們?yōu)橐粋€(gè)泛型類所指定的類型集合提供了一個(gè)有用的類型范圍。對泛型類 ArrayList 而言,對于任意(引用)類型 T ArrayList<?> 類型是 ArrayList<T> 的超類型(類似原始類型 ArrayList 和根類型 Object ,但是這些超類型在執(zhí)行類型推斷方面不是很有用)。

通配符類型 List<?> 與原始類型 List 和具體類型 List<Object> 都不相同。如果說變量 x 具有 List<?> 類型,這表示存在一些 T 類型,其中 x List<T> 類型, x 具有相同的結(jié)構(gòu),盡管我們不知道其元素的具體類型。這并不表示它可以具有任意內(nèi)容,而是指我們并不了解內(nèi)容的類型限制是什么 — 但我們知道 存在 某種限制。另一方面,原始類型 List 是異構(gòu)的,我們不能對其元素有任何類型限制,具體類型 List<Object> 表示我們明確地知道它能包含任何對象(當(dāng)然,泛型的類型系統(tǒng)沒有 “列表內(nèi)容” 的概念,但可以從 List 之類的集合類型輕松地理解泛型)。

通配符在類型系統(tǒng)中的作用部分來自其不會(huì)發(fā)生協(xié)變(covariant)這一特性。數(shù)組是協(xié)變的,因?yàn)? Integer Number 的子類型,數(shù)組類型 Integer[] Number[] 的子類型,因此在任何需要 Number[] 值的地方都可以提供一個(gè) Integer[] 值。另一方面,泛型不是協(xié)變的, List<Integer> 不是 List<Number> 的子類型,試圖在要求 List<Number> 的位置提供 List<Integer> 是一個(gè)類型錯(cuò)誤。這不算很嚴(yán)重的問題 — 也不是所有人都認(rèn)為的錯(cuò)誤 — 但泛型和數(shù)組的不同行為的確引起了許多混亂。

我已使用了一個(gè)通配符 — 接下來呢?

清單 1 展示了一個(gè)簡單的容器(container)類型 Box ,它支持 put get 操作。 Box 由類型參數(shù) T 參數(shù)化,該參數(shù)表示 Box 內(nèi)容的類型, Box<String> 只能包含 String 類型的元素。

清單 1. 簡單的泛型 Box 類型

                      
public interface Box<T> {
    public T get();
    public void put(T element);
}

    

通配符的一個(gè)好處是允許編寫可以操作泛型類型變量的代碼,并且不需要了解其具體類型。例如,假設(shè)有一個(gè) Box<?> 類型的變量,比如清單 2 unbox() 方法中的 box 參數(shù)。 unbox() 如何處理已傳遞的 box?

清單 2. 帶有通配符參數(shù)的 Unbox 方法

                      
public void unbox(Box<?> box) {
    System.out.println(box.get());
}

    

事實(shí)證明 Unbox 方法能做許多工作:它能調(diào)用 get() 方法,并且能調(diào)用任何從 Object 繼承而來的方法(比如 hashCode() )。它惟一不能做的事是調(diào)用 put() 方法,這是因?yàn)樵诓恢涝? Box 實(shí)例的類型參數(shù) T 的情況下它不能檢驗(yàn)這個(gè)操作的安全性。由于 box 是一個(gè) Box<?> 而不是一個(gè)原始的 Box ,編譯器知道存在一些 T 充當(dāng) box 的類型參數(shù),但由于不知道 T 具體是什么,您不能調(diào)用 put() 因?yàn)椴荒軝z驗(yàn)這么做不會(huì)違反 Box 的類型安全限制(實(shí)際上,您可以在一個(gè)特殊的情況下調(diào)用 put() :當(dāng)您傳遞 null 字母時(shí)。我們可能不知道 T 類型代表什么,但我們知道 null 字母對任何引用類型而言是一個(gè)空值)。

關(guān)于 box.get() 的返回類型, unbox() 了解哪些內(nèi)容呢?它知道 box.get() 是某些未知 T T ,因此它可以推斷出 get() 的返回類型是 T 的擦除(erasure),對于一個(gè)無上限的通配符就是 Object 。因此清單 2 中的表達(dá)式 box.get() 具有 Object 類型。

通配符捕獲

清單 3 展示了一些似乎 應(yīng)該 可以工作的代碼,但實(shí)際上不能。它包含一個(gè)泛型 Box 、提取它的值并試圖將值放回同一個(gè) Box

清單 3. 一旦將值從 box 中取出,則不能將其放回

                      
public void rebox(Box<?> box) {
    box.put(box.get());
}

Rebox.java:8: put(capture#337 of ?) in Box<capture#337 of ?> cannot be applied
   to (java.lang.Object)
    box.put(box.get());
       ^
1 error

    

這個(gè)代碼看起來應(yīng)該可以工作,因?yàn)槿〕鲋档念愋头戏呕刂档念愋停欢幾g器生成(令人困惑的)關(guān)于 “capture#337 of ?” 與 Object 不兼容的錯(cuò)誤消息。

“capture#337 of ?” 表示什么?當(dāng)編譯器遇到一個(gè)在其類型中帶有通配符的變量,比如 rebox() box 參數(shù),它認(rèn)識到必然有一些 T ,對這些 T 而言 box Box<T> 。它不知道 T 代表什么類型,但它可以為該類型創(chuàng)建一個(gè)占位符來指代 T 的類型。占位符被稱為這個(gè)特殊通配符的 捕獲(capture) 。這種情況下,編譯器將名稱 “capture#337 of ?” 以 box 類型分配給通配符。每個(gè)變量聲明中每出現(xiàn)一個(gè)通配符都將獲得一個(gè)不同的捕獲,因此在泛型聲明 foo(Pair<?,?> x, Pair<?,?> y) 中,編譯器將給每四個(gè)通配符的捕獲分配一個(gè)不同的名稱,因?yàn)槿我馕粗念愋蛥?shù)之間沒有關(guān)系。

錯(cuò)誤消息告訴我們不能調(diào)用 put() ,因?yàn)樗荒軝z驗(yàn) put() 的實(shí)參類型與其形參類型是否兼容 — 因?yàn)樾螀⒌念愋褪俏粗摹T谶@種情況下,由于 ? 實(shí)際表示 “?extends Object” ,編譯器已經(jīng)推斷出 box.get() 的類型是 Object ,而不是 “capture#337 of ?”。它不能靜態(tài)地檢驗(yàn)對由占位符 “capture#337 of ?” 所識別的類型而言 Object 是否是一個(gè)可接受的值。

捕獲助手

雖然編譯器似乎丟棄了一些有用的信息,我們可以使用一個(gè)技巧來使編譯器重構(gòu)這些信息,即對未知的通配符類型命名。清單 4 展示了 rebox() 的實(shí)現(xiàn)和一個(gè)實(shí)現(xiàn)這種技巧的泛型助手方法(helper):

清單 4. “捕獲助手” 方法

                      
public void rebox(Box<?> box) {
    reboxHelper(box);
}

private<V> void reboxHelper(Box<V> box) {
    box.put(box.get());
}

    

助手方法 reboxHelper() 是一個(gè) 泛型方法 ,泛型方法引入了額外的類型參數(shù)(位于返回類型之前的尖括號中),這些參數(shù)用于表示參數(shù)和/或方法的返回值之間的類型約束。然而就 reboxHelper() 來說,泛型方法并不使用類型參數(shù)指定類型約束,它允許編譯器(通過類型接口)對 box 類型的類型參數(shù)命名。

捕獲助手技巧允許我們在處理通配符時(shí)繞開編譯器的限制。當(dāng) rebox() 調(diào)用 reboxHelper() 時(shí),它知道這么做是安全的,因?yàn)樗陨淼? box 參數(shù)對一些未知的 T 而言一定是 Box<T> 。因?yàn)轭愋蛥?shù) V 被引入到方法簽名中并且沒有綁定到其他任何類型參數(shù),它也可以表示任何未知類型,因此,某些未知 T Box<T> 也可能是某些未知 V Box<V> (這和 lambda 積分中的 α 減法原則相似,允許重命名邊界變量)。現(xiàn)在 reboxHelper() 中的表達(dá)式 box.get() 不再具有 Object 類型,它具有 V 類型 — 并允許將 V 傳遞給 Box<V>.put()

我們本來可以將 rebox() 聲明為一個(gè)泛型方法,類似 reboxHelper() ,但這被認(rèn)為是一種糟糕的 API 設(shè)計(jì)樣式。此處的主要設(shè)計(jì)原則是 “如果以后絕不會(huì)按名稱引用,則不要進(jìn)行命名”。就泛型方法來說,如果一個(gè)類型參數(shù)在方法簽名中只出現(xiàn)一次,它很有可能是一個(gè)通配符而不是一個(gè)命名的類型參數(shù)。一般來說,帶有通配符的 API 比帶有泛型方法的 API 更簡單,在更復(fù)雜的方法聲明中類型名稱的增多會(huì)降低聲明的可讀性。因?yàn)樵谛枰獣r(shí)始終可以通過專有的捕獲助手恢復(fù)名稱,這個(gè)方法讓您能夠保持 API 整潔,同時(shí)不會(huì)刪除有用的信息。

類型推斷

捕獲助手技巧涉及多個(gè)因素:類型推斷和捕獲轉(zhuǎn)換。Java 編譯器在很多情況下都不能執(zhí)行類型推斷,但是可以為泛型方法推斷類型參數(shù)(其他語言更加依賴類型推斷,將來我們可以看到 Java 語言中會(huì)添加更多的類型推斷特性)。如果愿意,您可以指定類型參數(shù)的值,但只有當(dāng)您能夠命名該類型時(shí)才可以這樣做 — 并且不能夠表示捕獲類型。因此要使用這種技巧,要求編譯器能夠?yàn)槟茢囝愋汀2东@轉(zhuǎn)換允許編譯器為已捕獲的通配符產(chǎn)生一個(gè)占位符類型名,以便對它進(jìn)行類型推斷。

當(dāng)解析一個(gè)泛型方法的調(diào)用時(shí),編譯器將設(shè)法推斷類型參數(shù)它能達(dá)到的最具體類型。 例如,對于下面這個(gè)泛型方法:

      public static<T> T identity(T arg) { return arg }; 

    

和它的調(diào)用:

      Integer i = 3;
System.out.println(identity(i));

    

編譯器能夠推斷 T Integer Number 、 Serializable 或 Object ,但它選擇 Integer 作為滿足約束的最具體類型。

當(dāng)構(gòu)造泛型實(shí)例時(shí),可以使用類型推斷減少冗余。例如,使用 Box 類創(chuàng)建 Box<String> 要求您指定兩次類型參數(shù) String

      Box<String> box = new BoxImpl<String>();

    

即使可以使用 IDE 執(zhí)行一些工作,也不要違背 DRY(Don't Repeat Yourself)原則。然而,如果實(shí)現(xiàn)類 BoxImpl 提供一個(gè)類似清單 5 的泛型工廠方法(這始終是個(gè)好主意),則可以減少客戶機(jī)代碼的冗余:

清單 5. 一個(gè)泛型工廠方法,可以避免不必要地指定類型參數(shù)

                      
public class BoxImpl<T> implements Box<T> {

    public static<V> Box<V> make() {
        return new BoxImpl<V>();
    }

    ...
}

    

如果使用 BoxImpl.make() 工廠實(shí)例化一個(gè) Box ,您只需要指定一次類型參數(shù):

      Box<String> myBox = BoxImpl.make();

    

泛型 make() 方法為一些類型 V 返回一個(gè) Box<V> ,返回值被用于需要 Box<String> 的上下文中。編譯器確定 String V 能接受的滿足類型約束的最具體類型,因此此處將 V 推斷為 String 。您還可以手動(dòng)地指定 V 的值:

      Box<String> myBox = BoxImpl.<String>make();

    

除了減少一些鍵盤操作以外,此處演示的工廠方法技巧還提供了優(yōu)于構(gòu)造函數(shù)的其他優(yōu)勢:您能夠?yàn)樗鼈兲岣吒呙枋鲂缘拿Q,它們能夠返回命名返回類型的子類型,它們不需要為每次調(diào)用創(chuàng)建新的實(shí)例,從而能夠共享不可變的實(shí)例(參見 參考資料 中的 Effective Java, Item #1,了解有關(guān)靜態(tài)工廠的更多優(yōu)點(diǎn))。

結(jié)束語

通配符無疑非常復(fù)雜:由 Java 編譯器產(chǎn)生的一些令人困惑的錯(cuò)誤消息都與通配符有關(guān),Java 語言規(guī)范中最復(fù)雜的部分也與通配符有關(guān)。然而如果使用適當(dāng),通配符可以提供強(qiáng)大的功能。此處列舉的兩個(gè)技巧 — 捕獲助手技巧和泛型工廠技巧 — 都利用了泛型方法和類型推斷,如果使用恰當(dāng),它們能顯著降低復(fù)雜性。

參考資料

學(xué)習(xí)

  • 您可以參閱本文在 developerWorks 全球站點(diǎn)上的 英文原文
  • Java 理論與實(shí)踐 (Brian Goetz,developerWorks):參閱該系列的所有文章。
  • 了解泛型 ”(Brian Goetz,developerWorks,2005 年 1 月):了解如何在學(xué)習(xí)使用泛型時(shí)識別和避免一些陷阱。
  • JDK 5.0 中的泛型介紹 (Brian Goetz,developerWorks,2004 年 12 月):developerWorks 投稿人和 Java 編程專家 Brian Goetz 解釋了將泛型添加到 Java 語言的動(dòng)機(jī)、語法細(xì)節(jié)和泛型類型的語義,并介紹了如何在自己的類中使用泛型。
  • JSR 14 :將泛型添加到 Java 編程語言中。早期的規(guī)范來源于 GJ 通配符 是后來添加的。
  • Java Generics and Collections :提供了一個(gè)全面的泛型處理。
  • Effective Java : Item 1 進(jìn)一步探討了靜態(tài)工廠方法的優(yōu)點(diǎn)。
  • Generics FAQ : Angelika Langer 創(chuàng)建了關(guān)于泛型的完整 FAQ。
  • Java Concurrency in Practice :使用 Java 代碼開發(fā)并發(fā)程序的 how-to 手冊,包括構(gòu)造和組成線程安全的類和程序、避免風(fēng)險(xiǎn)、管理性能和測試并發(fā)應(yīng)用程序。
  • 技術(shù)書店 :瀏覽有關(guān)各種技術(shù)主題的書籍。
  • Java 技術(shù)專區(qū) :數(shù)百篇關(guān)于 Java 編程各個(gè)方面的文章。

討論

關(guān)于作者

Brian Goetz 作為一名專業(yè)軟件開發(fā)人員已經(jīng) 20 年了。他是 Sun Microsystems 的高級工程師,并且效力于多個(gè) JCP 專家組。Brian 的著作 Java Concurrency In Practice 在 2006 年 5 月由 Addison-Wesley 出版。請參閱 Brian 在流行的業(yè)界出版物上 已發(fā)表和即將發(fā)表的文章

Java 理論與實(shí)踐: 使用通配符簡化泛型使用

Java 理論與實(shí)踐: 使用通配符簡化泛型使用


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會(huì)非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 国产乱子伦一级毛片 | 免费看日韩欧美一级毛片 | 噜噜嘿在线视频免费观看 | 久久久精品久久久久特色影视 | 日本一级爽爽爽爽 | 久久久伊香蕉网站 | 色视频在线 | 九九视频只有精品 | 凹凸精品视频分类国产品免费 | 国产自产视频 | 狼人综合伊人 | 欧美日韩亚洲国产精品 | 日韩久久网 | 成人小视频在线观看免费 | 国产娱乐凹凸视觉盛宴在线视频 | 天天干夜啪 | 精品国产免费观看 | 全部在线播放免费毛片 | 日韩毛片大全 | 热久久在线观看 | 亚洲精品欧美精品 | 熟妇毛茸茸xxxoo | 国内久久久久影院精品 | 免费看国产精品久久久久 | 中文字幕不卡一区2021 | 日韩精品一区二区三区免费视频 | 亚洲综合久久久 | 华人亚洲欧美精品国产 | 最近中文字幕无吗免费视频 | 操操操干干 | 色综合视频一区二区三区 | 久久亚洲国产精品一区二区 | 国产性做久久久久久 | 成年ssswww中国女人 | 成人免费视频网 | 在线色国产 | 午夜免费播放观看在线视频 | 日韩欧美国产精品第一页不卡 | 日日摸夜夜添夜夜添一区二区 | 久久精品综合视频 | 欧美日韩在线观看视频 |