隨筆 - 17  文章 - 49  trackbacks - 0
          <2006年8月>
          303112345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          常用鏈接

          留言簿(1)

          隨筆分類(17)

          隨筆檔案(17)

          相冊

          最新隨筆

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          2006 8 9 星期三

          管中窺虎

          在學習 java 1.5 的過程中,我使用了 sun 公布的 tutorial ,這份文檔寫的比較詳盡易明,但是對于想快速了解 tiger 而且具有較好 java 基礎的人來說,大篇幅的英文文檔是比較耗時間和非必需的,所以我將會歸納這份文檔的主要內容,在保證理解的底線上,盡力減少閱讀者需要的時間。

          ?

          在以下地址可以進入各新增語言特色介紹以及下載相關文檔(若有)。

          http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html

          ?

          這一篇是接著上文繼續的,在這里補充說明,雖然我希望以雙語寫作,但是把英文文檔翻譯過來后再翻譯回去,似乎是件好傻的事情。。。所以這些翻譯并精簡的文章算是個例外吧。

          第一道虎紋: generic -泛型 / 類屬(二)

          泛型方法

          假設我們想把一個數組的元素都放到一個容器類里,下面是第一次嘗試:

          static ? void ?fromArrayToCollection(Object[]?a,?Collection? < ? ? ? > ?c)? {?

          ??
          for ?(Object?o?:?a)? {?
          ?????????c.add(o);?
          // ?compile?time?error?

          ??}

          }
          ?

          現在你應該學會了不去犯初學者的錯誤,用

          Collection < Object > 來作為參數,你也許也發現了用 Collection < ? > 也不成,不知道就是不知道,不能放東西進去。

          好了,主角登場:泛型方法

          static ? < ?T? > ? void ?fromArrayToCollection(T[]?a,?Collection? < ?T? > ?c)? {?

          ??
          for ?(T?o?:?a)? {?
          ?????????c.add(o);?
          // ?correct?

          ????}

          }
          ?

          方法的聲明加入了類型參數,在上面這個方法里,如果

          c 的元素類型是 a 的元素類型的父類,就能成功執行方法。下面這些代碼可以幫助你了解一下:

          Object[]?oa? = ? new ?Object[ 100 ];?

          Collection?
          < ?Object? > ?co? = ? new ?ArrayList? < ?Object? > ?();?

          fromArrayToCollection(oa,?co);?
          // ?T?inferred?to?be?Object?

          String[]?sa?
          = ? new ?String[ 100 ];?

          Collection?
          < ?String? > ?cs? = ? new ?ArrayList? < ?String? > ?();?

          fromArrayToCollection(sa,?cs);?
          // ?T?inferred?to?be?String?

          fromArrayToCollection(sa,?co);?
          // ?T?inferred?to?be?Object?

          Integer[]?ia?
          = ? new ?Integer[ 100 ];?

          Float[]?fa?
          = ? new ?Float[ 100 ];?

          Number[]?na?
          = ? new ?Number[ 100 ];?

          Collection?
          < ?Number? > ?cn? = ? new ?ArrayList? < ?Number? > ?();?

          fromArrayToCollection(ia,?cn);?
          // ?T?inferred?to?be?Number?

          fromArrayToCollection(fa,?cn);?
          // ?T?inferred?to?be?Number?

          fromArrayToCollection(na,?cn);?
          // ?T?inferred?to?be?Number?

          fromArrayToCollection(na,?co);?
          // ?T?inferred?to?be?Object?

          fromArrayToCollection(na,?cs);?
          // ?compile-time?error?

          ??

          注意到我們并沒有真正的傳入一個類型實參,而是由編譯器以方法的實際參數對象來推斷,它推斷出使得這次方法調用成立的類型,并盡可能地特化這個類型。比如說如果

          T 推斷為 Number 依然成立的時候,就不會推斷為 Object

          現在看來,泛型方法和通配符有些共通的地方,使得類屬有一定的靈活性。那么什么時候用泛型方法,什么時候用通配符?看看下面的例子:

          ?interface?Collection?<?E?>?{?

          ???
          public?boolean?containsAll(Collection?<???>?c);?

          ???
          public?boolean?addAll(Collection?<???extends?E?>?c);?

          }
          ?

          以及:

          ?interface?Collection?<?E?>?{?

          ???
          public?<?T?>?boolean?containsAll(Collection?<?T?>?c);?

          ???
          public?<?T?extends?E?>?boolean?addAll(Collection?<?T?>?c);?

          ???
          //?注意類型變量也可以有上限哦~?

          }
          ?

          這兩種方式都達成了同樣的目的,使得方法有了多態性。然而注意到在每個方法的聲明中,

          T 只出現了一次,這種情況下就應該用通配符,通配符的主要目的就是提供彈性的泛化,而多態方法則用于表達兩個或多個參數間的依賴關系,你回過頭去看多態方法的第一個例子,是不是這個情況?如果不存在依賴關系需要表達,就不應該用多態方法,因為從可讀性上來說,通配符更清晰。

          ?

          而且有趣的是,它們并非水火不容,反而可以精妙配合,如下:

          ?class?Collections?{?

          ??
          public?static?<?T?>?void?copy(List?<?T?>?dest,?List?<???extends?T?>?src)?{??}?

          }
          ?

          這個合作使得

          dest src 的依賴關系得以表達,同時讓 src 的接納范疇擴大了。假如我們只用泛型方法來實現:

          ?class?Collections?{?

          ???
          public?static?<?T,?S?extends?T?>?void?copy(List?<?T?>?dest,?List?<?S?>?src)?{??}?

          }
          ?

          那么

          S 的存在就顯得有些不必要,有些不優雅。總的來說,通配符更簡潔清晰,只要情況允許就應該首選。

          ?

          下面是給幾何圖形家族添加了一個有記憶功能的繪圖方法,展示了泛型方法的使用,有興趣就看看,略過也不影響下一步的學習。

          ?

          static ?List? < ?List? < ? ? ? extends ?Shape? >> ?history? = ? new ?ArrayList? < ?List? < ? ? ? extends ?Shape? >> ?();?

          ???
          public ? void ?drawAll(List? < ? ? ? extends ?Shape? > ?shapes)? {?

          ???????????history.addLast(shapes);?

          ???
          ????for?(Shape?s:?shapes)?{?

          ???????????s.draw(?
          this?);?

          ??????????}
          ?

          }
          ?

          ??

          ?

          這里又再談談命名規范的事情,用 T 來表示類型( type )就是個很好的選擇,假如已經沒有更多的背景信息的話,而在泛型方法里就是這樣子,我們只是想表達一個類型。那么如果有多個參數出現,那么用 T 的街坊鄰里就不錯, S 啊,什么的。如果方法出現在一個泛型類里,就主要避免類的泛型變量和方法的泛型變量同名混淆,同樣的,泛型類的嵌套泛型類也應該注意。

          ?

          和遺老們打交道

          很顯然的,這個星球上存在的 java 代碼里,沒有引入泛型的還是多數,它們也不會一夜走進新社會,怎么把它們轉換為泛型的會在晚些再談及,現在我們談談和它們打交道的事情。這包括兩方面:在引入泛型的代碼里使用老代碼,在老代碼上使用引入了泛型的代碼。

          ?

          首先看看前者,看例子:

          public ? interface ?Part? {??} ?

          public ? class ?Inventory? {? /** ?

          *?Adds?a?new?Assembly?to?the?inventory?database.?

          *?The?assembly?is?given?the?name?name,?and?consists?of?a?set?

          *?parts?specified?by?parts.?All?elements?of?the?collection?parts?

          *?must?support?the?Part?interface.?

          *
          */
          ?

          public ? static ? void ?addAssembly(String?name,?Collection?parts)? {??} ?

          public ? static ?Assembly?getAssembly(String?name)? {??} ?

          }
          ?

          public ? interface ?Assembly? {?

          ???Collection?getParts();?
          // ?Returns?a?collection?of?Parts?

          }
          ?

          public ? class ?Blade? implements ?Part?? {} ?

          public ? class ?Guillotine? implements ?Part? {} ?

          public ? class ?Main? {?

          ?public?static?void?main(String[]?args)?{?

          ???Collection?
          <?Part?>?c?=?new?ArrayList?<?Part?>?();?

          ???c.add(
          new?Guillotine())?;?

          ???c.add(
          new?Blade());?

          ???Inventory.addAssembly(”thingee”,?c);?

          ???Collection?
          <?Part?>?k?=?Inventory.getAssembly(”thingee”).getParts();//?琢磨一下這里??

          }
          ?

          }
          ?

          上面的代碼有沒有問題?如果前面的內容里你沒打瞌睡,你應該發現注解處的那個語句很有問題,類型不安全問題。我們稱Collection這種不帶類型參數的使用叫做原始類型,編譯器無法保證這樣子的容器類放了什么東西,但是編譯器會以不那么嚴格的標準去要求這個舊社會的人,否則,老代碼將完全不能在1.5里使用,編譯器會發出一個unchecked warning,怎么處理由你來決定。這樣的設計是符合實際的,否則就是和已有代碼徹底決裂。

          雖然這樣的調用會有錯誤的風險,但總比你完全不用泛型機制好,因為至少你保證了在你的這一端的類型安全,而且總有一天,英特那雄耐爾一定會實現。。。。 J

          嚴肅地回到我們的話題,既然有風險,那么當你得到了這樣的警告時,小心檢查則是目前最好的對策。

          但是,如果你不理會一個這樣的警告,而且事實上你真的犯了一個類型安全的錯誤,會發生什么呢?

          消除與翻譯

          ?

          ?public?String?loophole(Integer?x)?{?List?<?String?>?ys?=?new?LinkedList?<?String?>?();?

          ???List?xs?
          =?ys;?

          ??????xs.add(x);?
          //?compile-time?unchecked?warning?

          ??????
          return?ys.iterator().next();?

          }
          ?

          這個警告所在的地方確實是有問題的,一個

          Integer 被放入了一個 List 中,這個 List 只是原始類型,所以編譯器只能給個警告,但事實上它又是指向了 ys 指向的對象,一個只放 String List ,在最后一句里,會發生什么事情?實際運行起來,這段代碼就等同于:

          ?public?String?loophole(Integer?x)?{?

          ???List?ys?
          =?new?LinkedList;?

          ???List?xs?
          =?ys;?

          ???xs.add(x);?

          ???
          return?(String)?ys.iterator().next();?//?run?time?error?

          }
          ?

          它們會得到同樣的錯誤:一個

          ClassCastException

          為什么呢?因為泛型在 java 編譯器里是以一種稱為“消除”的前端轉換實現的,你幾乎,我說幾乎,可以認為是一種代碼到代碼的翻譯,象上面這樣,把帶泛型的版本翻譯成不帶泛型的版本,接下來,當代碼的執行交到 JVM 的手里時,它可不管你是哪朝代的人,有錯就是有錯,類型安全的基本政策不動搖,即使你手里拽著 unchecked warnings 的證明。

          基本上,消除機制就是把類型信息都扔掉了, List<String> 變成了 List ,上限通常都變成了 Object ,而且,當轉換后的代碼不符合泛型里的類型限制時,就添加一個類型轉換,就是上例中那個 String 的轉換會出現的原因。

          消除機制的細節不是這里討論的內容,這里簡單的讓你了解一些需要了解的情況而已。

          ?

          ?

          假如你的代碼更新為:

          ?public?String?loophole(Integer?x)?{?

          ???List?ys?
          =?new?LinkedList;?

          ???List?xs?
          =?ys;?

          ???xs.add(x);?

          ????return?(String)?ys.iterator().next();?//?run?time?error?

          }
          ?

          而調用這個代碼的老客戶代碼是:

          public ? class ?Blade? implements ?Part?? {?

          }
          ?

          public ? class ?Guillotine? implements ?Part? {?

          }
          ?

          public ? class ?Main? {? public ? static ? void ?main(String[]?args)? {?

          ???Collection?c?
          = ? new ?ArrayList();?

          ???c.add(
          new ?Guillotine())?;?

          ???c.add(
          new ?Blade());?

          ???Inventory.addAssembly(”thingee”,?c);?
          // ?1:?unchecked?warning?

          ???Collection?k?
          = ?Inventory.getAssembly(”thingee”).getParts();?

          }


          ?

          ?

          也就是在老代碼里調用泛型化了的代碼。

          如注解 1 所示,會有警告出現,原因你也已經了解了。你可以在編譯時選擇用 1.4 的環境,那么就不會有警告出現,但同樣你也失去了 1.5 帶來的各種新特色了。

          ?

          這一篇到此為止,下一篇將看看一些還需要補充說明的地方,但并不意味著內容會很簡單。

          ?

          posted on 2006-08-09 17:45 Ye Yiliang 閱讀(1001) 評論(0)  編輯  收藏 所屬分類: Java
          主站蜘蛛池模板: 贡山| 乡城县| 红桥区| 桦川县| 娱乐| 正镶白旗| 黄冈市| 新化县| 即墨市| 贡嘎县| 凤凰县| 成都市| 广宁县| 惠来县| 鲁山县| 平顺县| 驻马店市| 海阳市| 城市| 西充县| 沽源县| 雅江县| 环江| 芜湖县| 正安县| 石楼县| 上饶县| 镇坪县| 承德县| 普兰店市| 嘉善县| 台前县| 博兴县| 常宁市| 东明县| 枣庄市| 介休市| 潼关县| 高陵县| 博爱县| 甘孜|