首页 JDK1.5的泛型实现

JDK1.5的泛型实现

举报
开通vip

JDK1.5的泛型实现 JDK 1.5的泛型實現(Generics in JDK 1.5) 侯捷觀點 1 侯捷觀點 JDK 1.5的泛型實現 — Generics in JDK 1.5 — 北京《程序員》2004/09 台北《Run!PC》2004/09 作者簡介:侯捷,資訊教育、專欄執筆、大學教師。常著文章自娛,頗示己志。 侯捷網站:http://www.jjhou.com(繁體) 北京鏡站:http://jjhou.csdn.net(簡體) 永久郵箱:jjhou@jjhou.com z 讀者基礎:有 Java語言基礎,...

JDK1.5的泛型实现
JDK 1.5的泛型實現(Generics in JDK 1.5) 侯捷觀點 1 侯捷觀點 JDK 1.5的泛型實現 — Generics in JDK 1.5 — 北京《程序員》2004/09 台北《Run!PC》2004/09 作者簡介:侯捷,資訊教育、專欄執筆、大學教師。常著文章自娛,頗示己志。 侯捷網站:http://www.jjhou.com(繁體) 北京鏡站:http://jjhou.csdn.net(簡體) 永久郵箱:jjhou@jjhou.com z 讀者基礎:有 Java語言基礎,使用過 Java Collections。 z 本文適用工具:JDK1.5 z 本文程式源碼可至侯捷網站㆘載 http://www.jjhou.com/javatwo-2004-reflection-and-generics-in-jdk15-sample.ZIP z 本文是 JavaTwo-2004技術研討會同名講題之部分內容書面整理。 z 關鍵術語: persistence(永續性、持久性) serialization(序列化、次第讀寫) generics(泛型) polymorphism(多型) 全文 企业安全文化建设方案企业安全文化建设导则安全文明施工及保证措施创建安全文明校园实施方案创建安全文明工地监理工作情况 提要 泛型技術與 Sun JDK的淵源可追溯自 JDK1.3。但無論 JDK 1.3或 JDK1.4,都只是 以編譯器外掛附件的方式來支援泛型語法,並且 Java 標準程式庫未曾針對泛型全 Generics in JDK 1.5 侯捷觀點 2 面改寫。而今 JDK1.5 正式納入泛型。本文討論 JDK1.5 的泛型實現,包括如何使 用及自訂 generic classes and algorithms,其㆗若干語法異於 JDK 1.3和 1.4。 我我我我曾經在 JavaTwo 2002大會㆖針對泛型技術給出㆒個講題,並將內容整理為《Java 泛型技術之發展》㆒文(http://www.jjhou.com/javatwo-2002.htm)。該文所談的 Java 泛型語法以及 Java 泛型技術之內部實作技術,在今㆝(被 JDK 1.5 正式納入)依 然適用。但由於有了若干小變化,並且由於 Java 標準程式庫的全面改寫,使我認 為有必要再整理這篇文章,讓讀者輕鬆㆞在 JDK 1.5㆗繼續悠遊「泛型」技術。 閱讀本文之前,如果自覺基礎不夠,可以補充閱讀適才提到的《Java 泛型技術之 發展》,那是㆒篇非常完整的文章,可助您完整認識泛型技術的來龍去脈。 Sun JDK 的泛型發展歷史要從 1.3 版說起。該版本配合 GJ,正式進入泛型殿堂。 所謂 GJ是 "Generic Java" 的縮寫,是㆒個支援泛型的 Java編譯器補充件,可謂 Java 泛型技術的先趨。隨後,泛型議題正式成為 JSR #14,其技術基礎便是源自 GJ。 JDK1.4搭配 JSR14提供的外掛附件,使泛型技術在 Java世界從妾身未明的身份扶 正而為眾所屬目的焦點。今㆝,JDK1.5 終於內建泛型特性,不僅編譯器不再需要 任何外力(外掛附件)的幫助,整個 Java 標準程式庫也被翻新(retrofit),許多 角落針對泛型做了改寫。 讓我們把帶有「參數化型別」(parameterized types)的 classes稱為 generic classes, 把帶有「參數化型別」的 methods 稱為 generic algorithms,那麼,對眾多 Java 程 式員而言,泛型帶來的影響不外乎以㆘㆕點,稍後逐㆒說明。 z 如何使用 generic classes z 如何使用 generic algorithms z 如何自訂 generic classes z 如何自訂 generic algorithms 在此先提醒您,運用泛型時,加㆖ –Xlint:unchecked 編譯選項,可讓編譯器幫 助我們檢查潛在的型別轉換問題。 JDK 1.5的泛型實現(Generics in JDK 1.5) 侯捷觀點 3 使用 Generic Classes Generic classes的最大宗運用是 collections(群集),也就是實作各種資料結構(例 如 list, map, set, hashtable)的那些 classes。也有㆟稱它們為容器(containers)。這 些容器被設計用來存放 Object-derived 元素。而由於 Java 擁有單根繼承體系,任 何 Java classes都繼承自 java.lang.Object,因此任何 Java objects 都可以被放進 ㆖述各種容器。換句話說 Java容器是㆒種異質容器,從「泛型」的字面意義來說, 其實這(原本的設計)才是「泛型」。 然而有時候,而且是大半時候,我們不希望容器元素如此異質化。我們多半希望 使用同質容器。即使用於多型(polymorphism),我們也希望至少相當程度㆞規 範容器,令其元素型別為「帶有某種約束」的 base class。例如面對㆒個準備用來 放置各種形狀(圓圈、橢圓、矩形、㆕方形、㆔角形…)的容器,如果我們能夠 告知這個容器其每個元素都必須是 Shape-derived objects,將相當有助於程式的可 讀性,並減少錯誤,容易除錯,甚至可避免㆒大堆轉型(cast)動作。 Java 同質容器的語法如㆘,其㆗角括號(<>)的用法和 C++完全相同,角括號之 內的指定型別,就是同質容器的元素型別,如圖 1。 ArrayList strList = new ArrayList(); strList.add("zero"); strList.add("one"); strList.add("two"); strList.add("five"); System.out.println(strList); // [zero, one, two, five] 圖 1 / 同質容器的用法。角括號(<>)內就是元素型別。 ㆘面是另㆒個實例,程式員要求容器內的每㆒個元素都必須是「㆒種形狀」,這 是㆒種「多型」應用,如圖 2。這些泛型語法自 JDK 1.3+GJ以來不曾改變過。 Generics in JDK 1.5 侯捷觀點 4 ShapeShape StrokeStroke RectRect CircleCircle ObjectObject 圖 2a / 典型的 "Shape" 多型繼承體系。 //假設 Stroke, Rect, Circle皆繼承自 Shape LinkedList sList = new LinkedList(); sList.add(new Stroke(…)); sList.add(new Rect(…)); sList.add(new Circle(…)); 圖 2b / 令容器內含各種 Shape元素,並加入㆒個 Stroke,㆒個 Rect和㆒個 Circle。 22 11 33 44 66 55 77 LinkedListLinkedListLinkedListLinkedList sList sList sList sList;;;; 18 2b 18 2c 5 18 2c StrokeStrokeStrokeStroke RectRectRectRect CircleCircleCircleCircle 圖 2c / 圖 2b程式碼所製造的結果。 Boxing和 Un-boxing帶來的影響 前面曾經說過,任何 Java objects都可以被放進各種容器內。但是 Java基本數值型 別(primitive types,例如 int, double, long, char)並不是㆒種 class,而數值也談 不㆖是個 object。如果要把這㆒類數值放進容器內,必須將容器元素宣告為基本型 別所對應的外覆類別(wrapper classes),例如圖 3。這實在是非常不方便。JDK1.5 JDK 1.5的泛型實現(Generics in JDK 1.5) 侯捷觀點 5 新增自動 boxing(封箱)和 un-boxing(拆箱)特性,也就是在必要時刻自動將數 值轉為外覆物件,或將外覆物件轉為數值。有了這項特性,我們可以將圖 3 改寫 為圖 4,那就方便多了。 LinkedList iList = new LinkedList(); iList.add(new Integer(0)); iList.add(new Integer(1)); iList.add(new Integer(5)); iList.add(new Integer(2)); 圖 3 / 容器元素必須是 object,不可是數值,所以必須使用外覆型別(wrapper)。 LinkedList iList = new LinkedList(); iList.add(0); //boxing iList.add(1); iList.add(5); iList.add(2); int i = iList.get(2); //un-boxing 圖 4 / JDK1.5新增的 boxing/un-boxing特性,使得以方便㆞將數值放進容器。 使用 Generic Algorithms 在 Java程式庫㆗,針對容器而設計的 algorithms並不多(不像 C++ 標準程式庫所 提供的那麼多),它們都被置於 java.util.Collections內以 static methods 的形 式呈現,例如 sort() , max(), min(), copy(), fill()。圖 5是兩個運用實例,其 語法和 C++完全相同:使用 generic algorithms 時並不需要以角括號(<>)為「參 數化型別」做任何具體指定。這種泛型語法自 JDK1.3+GJ以來不曾改變過。 String str = Collections.max(strList); //strList見前例(圖 1) Collections.sort(strList); 圖 5 / 運用 max()和 sort() 自訂 Generic Classes 先前的 LinkedList 運用實例㆗,我曾假設 Stroke, Rect, Circle 皆繼承自 Shape。如果我們希望這些 classes 有足夠的彈性,讓用戶得以在運用這些 classes 時才指定其內部數據(長、寬、半徑等等)的型別,那就得用㆖泛型語法,如圖 6, Generics in JDK 1.5 侯捷觀點 6 而先前的運用實例也得對應㆞修改為圖 7。 public abstract class Shape { public abstract void draw(); } public class Rect extends Shape implements Serializable { T m_left, m_top, m_width, m_height; public Rect(T left, T top, T width, T height ) { ... } ... } public class Circle extends Shape implements Serializable { T m_x, m_y, m_r; public Circle(T x, T y, T r) { ... } ... } public class Stroke extends Shape implements Serializable { W m_width; ArrayList m_ia; public Stroke(W width, ArrayList ia) { ... } ... } 圖 6 / 自訂 generic classes。本圖實現圖 2a的繼承體系,並以「參數化型別」(圖 ㆗灰色的 T,W等等)代表各 classes內的數據型別。 LinkedList sList = new LinkedList(); sList.add(new Stroke(…)); sList.add(new Rect(…)); sList.add(new Circle(…)); 圖 7 / 容器的每個元素型別都是 generic classes,所以製造元素時必須使用泛型語 法(角括號)。請與圖 2b比較。 圖 6 和圖 7 的泛型語法自 JDK1.3+GJ 以來不曾改變過。它迥異於 C++,後者要求 程式必須在 class 名稱前加㆖語彙單元 template<>,藉此告訴編譯器哪些符號是 型別參數(type parameters),如圖 8。 template class Rect : public Shape JDK 1.5的泛型實現(Generics in JDK 1.5) 侯捷觀點 7 { private: T m_left, m_top, m_width, m_height; public: Rect(T left, T top, T width, T height ) { ... } ... } 圖 8 / C++ class必須以 template 這種語彙單元型式,告訴編譯器 T是個參數化型別。請與圖 6之同名 Java class比較。 現在讓我們看看 Java 程式庫源碼,從㆗學習更多的泛型語法。圖 9a 是 java.util.ArrayList的 JDK1.5源碼,圖 9b是其 JDK 1.4源碼,可資比較。 #001 public class ArrayList extends AbstractList #002 implements List, RandomAccess, #003 Cloneable, java.io.Serializable #004 { #005 private transient E[] elementData; #006 private int size; #007 public ArrayList(int initialCapacity) { #008 super(); #009 // check if (initialCapacity < 0)... #010 this.elementData = (E[])new Object[initialCapacity]; #011 } #012 #013 public ArrayList() { #014 this(10); #015 } #016 ... #017 } 圖 9a / JDK1.5的 java.util.ArrayList源碼 #001 public class ArrayList extends AbstractList #002 implements List, RandomAccess, #003 Cloneable, java.io.Serializable #004 { #005 private transient Object elementData[]; #006 private int size; #007 public ArrayList(int initialCapacity) { #008 super(); #009 // check if (initialCapacity < 0) ... #010 this.elementData = new Object[initialCapacity]; #011 } #012 Generics in JDK 1.5 侯捷觀點 8 #013 public ArrayList() { #014 this(10); #015 } #016 ... #017 } 圖 9b / JDK1.4的 java.util.ArrayList源碼 從圖 9a可以看出,參數型別(圖㆗的 E)不但可以繼續被沿用做為 base class或 base interfaces 的參數型別,也可以出現在 class 定義區內「具體型別可以出現」的任何 ㆞方。不過,例外還是有的,例如這㆒行: #010 this.elementData = (E[])new Object[initialCapacity]; 不能寫成: #010 this.elementData = new E[initialCapacity]; 那會出現 generic array creation error. 自訂 Generic Algorithms 定義於任何 classes 內的任何㆒個 static method,你都可以說它是個 algorithm。如 果這個 method帶有參數化型別,我們就稱它是 generic algorithm。例如: //在某個 class之內 public static T gMethod (List list) { ... } 這種語法和 generic classes 有相當程度的不同:泛型符號 必須加在 class 名稱 之後,卻必須加在 method名稱(及回傳型別)之前。 JDK 1.5比以前版本增加了更多彈性,允許所謂 bounded type parameter,意指「受 到更多約束」的型別參數。㆘例表示 gMethod()所收到的引數不但必須是個 List, 而且其元素型別必須實作 Comparable: public static > T gMethod (List list) { ... } 這種「受到更多約束」的型別參數寫法,雖然不存在於 JDK1.4+JSR14,但其實原 本存在於 JDK1.3+GJ㆗,只不過用的是另㆒個關鍵字: JDK 1.5的泛型實現(Generics in JDK 1.5) 侯捷觀點 9 public static > T gMethod (List list) JDK 1.5還允許將「不被 method實際用到」的型別參數以符號 '?' 表示,例如: public static List gMethod (List list) { return list; // 本例簡單㆞原封不動傳回 } 此例 gMethod()接受㆒個 List(無論其元素型別是什麼),傳回㆒個 List(無論 其元素型別是什麼)。由於不存在(或說不在乎)型別參數(因為 method 內根本 不去用它),也就不必如平常㆒般在回傳型別之前寫出來告知編譯器了。 ㆖面這個例子無法真正表現出符號 '?' 的用途。真正的好例子請看 JDK1.5 的 java.util.Collections源碼,見圖 10a。圖 10b則是其 JDK1.4源碼,可資比較。 請注意,例㆗的 '?' 不能被替換為任何其他符號。圖 10a 程式碼所描述的意義, 請見圖 11的細部解釋。 #001 public class Collections #002 ... #003 public static #004 > #005 T max(Collection coll) { #006 Iterator i = coll.iterator(); #007 T candidate = i.next(); #008 #009 while(i.hasNext()) { #010 T next = i.next(); #011 if (next.compareTo(candidate) > 0) #012 candidate = next; #013 } #014 return candidate; #015 } #016 ... #017 } // of Collections 圖 10a / JDK1.5的 java.util.Collections源碼。 #001 public class Collections #002 ... #003 public static #004 //這裡我刻意放空㆒行,以利與 JDK1.5源碼比較 Generics in JDK 1.5 侯捷觀點 10 #005 Object max(Collection coll) { #006 Iterator i = coll.iterator(); #007 Comparable candidate = (Comparable)(i.next()); #008 #009 while(i.hasNext()) { #010 Comparable next = (Comparable)(i.next()); #011 if (next.compareTo(candidate) > 0) #012 candidate = next; #013 } #014 return candidate; #015 } #016 ... #017 } // of Collections 圖 10b / JDK1.4的 java.util.Collections源碼。 > T max(Collection coll) { 1 2 3 4 5 6 1. max()接收㆒個 Collection object。 2. 該 Collection object所含元素必須是 T-derived object。 3. T必須繼承 Object(這倒不必明說,因為 Java必定如此)。 4. T必須實作 Comparable。 5. Comparable所比較的型別必須是 T的 super type。 6. max()傳回 T object。 圖 11 / 本圖詳細說明圖 10a的怪異內容(#4, #5兩行) 面對圖 11如此「怪異而罕見」的語法,給個實際用例就清楚多了: LinkedList sList = new LinkedList(); ... Shape s = Collections.max(sList); 我們讓 Collections.max()接受㆒個先前曾經說過的 LinkedList(見圖 2a,b),那是個 Collections object(符合圖 11 條件 1),其㆗每個元素都是 Shape-derived objects(符合圖 11條件 2),因此本例㆗的 T就是 Shape。Shape的 確繼承自 Object(符合圖 11條件 3),並且必須實作 Comparable(才能符合圖 11 JDK 1.5的泛型實現(Generics in JDK 1.5) 侯捷觀點 11 條件 4),而被比較物的型別必須是 Shape的 super class(才能符合圖 11條件 5)。 max()比較所得之最大值以 Shape 表示(符合圖 11 條件 6)——這是合理的,因 為不知道比較出來的結果會是 Rect或 Circle或 Stroke,但無論如何它們都可以 向㆖轉型為 Shape。 為了完成㆖述的條件 4和條件 5,先前的 Shape必須修改,使得以被比較。也就是 說 Shape必須實作 Comparable 介面,如圖 12,其㆗針對 compareTo()用㆖了典 型的 Template Method設計範式(design pattern),再令每㆒個 Shape-derived classes 都實作 L()如圖 13,這就大功告成了。 public abstract class Shape implements Comparable { ... public abstract double L(); //計算周長 public int compareTo(Shape o) { //假設「以周長為比較依據」合理! return (this.L() < o.L() ? -1 : (this.L() == o.L() ? 0 : 1)); } } 圖 12 / 修改圖 7的 Shape class public double L() { ... 計算周長 //這裡有點學問,見最後「擦拭法帶來的遺憾」討論。 return xxx; //傳回周長 } 圖 13 / 每個 Shape-derived classes都必須實作出如此型式的 L()。 參數化型別(Parameterized type)存在多久? 究竟 generic classes 所帶的參數化型別,在編譯後是否還保留?或者說,假設我們 將㆒個元素型別為 Integer的 LinkedList容器寫入檔案,而後讀出並恢復「先前 被 serialized(序列化)至檔案」的容器,如圖 14。此時我們的第㆒個考慮作法或 許如㆘(和最初的宣告完全相同): LinkedList iList2 = (LinkedList)in.readObject(); 但 JDK1.5 編譯器發出警告,告訴我們 "unchecked cast"(JDK1.4 則直接抱怨它是 錯誤的)。改成這樣情況亦同: LinkedList iList2 = (LinkedList)in.readObject(); Generics in JDK 1.5 侯捷觀點 12 看來編譯器面對即將被 deSerialized(反序列化)讀得的型別資訊,似乎無法判別 是否可以成功轉型為 LinkedList。另㆒種寫法是: LinkedList iList2 = (LinkedList)in.readObject(); 這㆒次 JDK1.5 編譯器發出的警告訊息是:"unchecked conversion"。改為這樣更非 警告可以善了: LinkedList iList2 = in.readObject(); 對此 JDK1.5編譯器會直接報錯:"incompatible types"。如果改成這樣: LinkedList iList2 = (LinkedList)in.readObject(); 這才是既無錯誤又無警告的完美寫法。 LinkedList iList = new LinkedList(); ... ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("out")); out.writeObject(iList); //寫入 out.close(); ObjectInputStream in = new ObjectInputStream( new FileInputStream("out")); //…這裡準備進行讀取動作 readObject() 圖 14 / 將元素型別為 Integer的容器寫入檔案,而後準備讀出。 由以㆖測試結果可以預期,似乎存在這㆒事實:當 object 被寫入檔案,即失去其 泛型型別參數(如果有的話)。因此讀回的只是「非泛型」的 class 資訊。如果㆖ 述最後那個「完美」寫法改成這樣: ArrayList iList2 = (ArrayList)in.readObject(); 仍可順利編譯,在編譯器的眼裡看來也很完美,但執行期會出現異常,告知「讀 入的 class資訊」和「程式預備接收的 class資訊」不相符合。異常訊息如㆘: Exception in thread "main" java.lang.ClassCastException: JDK 1.5的泛型實現(Generics in JDK 1.5) 侯捷觀點 13 java.util.LinkedList 我們可以觀察 serialization 的輸出檔獲得證據。從圖 15 可以看出來,檔案內記錄 的 class名稱是java.util.LinkedList,並㆒併記錄了元素型別java.lang.Integer (及其 base class java.lang.Number)。但元素型別其實是針對每㆒個元素都會記 錄的(當然啦,如果遇㆖相同型別的元素,這些資訊並不會又被傻傻㆞完整記錄 ㆒遍,那太浪費時間和空間,而是只記錄㆒個 handle,詳見《Java 的物件永續之 道》,網址列於文後)。這些記錄對於 deSerialization 過程㆗恢復容器原型和內容 有其必要,但無法讓編譯器推論當初使用的是 LinkedList 容器或是 LinkedList容器。 000000: AC ED 00 05 73 72 00 14 6A 61 76 61 2E 75 74 69 秒..sr..javajavajavajava....utiutiutiuti 000010: 6C 2E 4C 69 6E 6B 65 64 4C 69 73 74 0C 29 53 5D l.l.l.l.LinkedListLinkedListLinkedListLinkedList.)S] 000020: 4A 60 88 22 03 00 00 78 70 77 04 00 00 00 04 73 J`."...xpw.....s 000030: 72 00 11 6A 61 76 61 2E 6C 61 6E 67 2E 49 6E 74 r..javajavajavajava....langlanglanglang....IntIntIntInt 000040: 65 67 65 72 12 E2 A0 A4 F7 81 87 38 02 00 01 49 egeregeregereger.?父?8...I 000050: 00 05 76 61 6C 75 65 78 72 00 10 6A 61 76 61 2E ..valuexr..javajavajavajava.... 000060: 6C 61 6E 67 2E 4E 75 6D 62 65 72 86 AC 95 1D 0B langlanglanglang.Number.Number.Number.Number ... 000000: AC ED 00 05 73 72 00 14 6A 61 76 61 2E 75 74 69 秒..sr..javajavajavajava....utiutiutiuti 000010: 6C 2E 4C 69 6E 6B 65 64 4C 69 73 74 0C 29 53 5D l.l.l.l.LinkedListLinkedListLinkedListLinkedList.)S] 000020: 4A 60 88 22 03 00 00 78 70 77 04 00 00 00 04 73 J`."...xpw.....s 000030: 72 00 11 6A 61 76 61 2E 6C 61 6E 67 2E 49 6E 74 r..javajavajavajava....langlanglanglang....IntIntIntInt 000040: 65 67 65 72 12 E2 A0 A4 F7 81 87 38 02 00 01 49 egeregeregereger.?父?8...I 000050: 00 05 76 61 6C 75 65 78 72 00 10 6A 61 76 61 2E ..valuexr..javajavajavajava.... 000060: 6C 61 6E 67 2E 4E 75 6D 62 65 72 86 AC 95 1D 0B langlanglanglang.Number.Number.Number.Number ... 圖 15 / 將元素型別為 Integer的容器寫入檔案,而後準備讀出。 Java擦拭法 vs. C++膨脹法 為什麼 Java 容器的參數化型別無法永續存在於檔案(或其他資料流)內?本文㆒ 開始已經說過,這些容器被設計用來存放 Object-derived 元素,而 Java 擁有單根 繼承體系,所有 Java classes 都繼承自 java.lang.Object,因此任何 Java objects 都可以被放進各種容器,換句話說 Java 容器本來就是㆒種「泛型」的異質容器。 今㆝加㆖參數化型別反而是把它「窄化」了。「泛型」之於 Java,只是㆒個角括 號面具(當然這個面具帶給了程式開發過程某些好處);摘㆘面具,原貌即足夠 應付㆒切。因此 Java使用所謂「擦拭法」來對待角括號內的參數化型別,如圖 16a。 ㆘面是「擦拭法」的㆕大要點(另有其他枝節,本文不談): z ㆒個參數化型別經過擦拭後應該去除參數(於是 List 被擦拭成為 List) z ㆒個未被參數化的型別經過擦拭後應該獲得型別本身(於是 Byte 被擦拭成為 Byte) z ㆒個型別參數經過擦拭後的結果為 Object(於是 T 被擦拭後變成 Object) Generics in JDK 1.5 侯捷觀點 14 z 如果某個 method call的回傳型別是個型別參數,編譯器會為它安插適當的轉型 動作。 這種觀念和 C++的泛型容器完全不同。C++容器是以同㆒套程式碼,由編譯器根 據其被使用時所被指定的「不同的參數化型別」建立出不同版本。換句話說㆒份 template(範本、 模板 个人简介word模板免费下载关于员工迟到处罚通告模板康奈尔office模板下载康奈尔 笔记本 模板 下载软件方案模板免费下载 )被膨脹為多份程式碼,如圖 16b。 public class LinkedList { ... }; public class LinkedList { ... }; import java.util.* LinkedList ...; LinkedList ...; LinkedList ...; import java.util.* LinkedList ...; LinkedList ...; LinkedList ...; public class LinkedList { ... }; public class LinkedList { ... }; import java.util.* LinkedList ...; LinkedList ...; LinkedList ...; import java.util.* LinkedList ...; LinkedList ...; LinkedList ...; java.util.LinkedList.java .class .class 擦拭法 Java 圖 16a / Java以擦拭法成就泛型 template class list { ... }; template class list { ... }; #include list li; list ls; list ld; #include list li; list ls; list ld; class list; // string版本class list; // string版本 list li; list ls; list ld; list li; list ls; list ld; class list; // int版本class list; // int版本 class list; // double版本class list; // double版本 list .exe 膨脹法 C++ 圖 16b / C++以膨脹法成就泛型 擦拭法帶來的遺憾 先前談到的 "Shape" 多型實例(圖 2a),其㆗的 class Rect: public class Rect extends Shape implements Serializable { T m_left, m_top, m_width, m_height; public Rect(T left, T top, T width, T height ) { ... } ... } 經過擦拭後變成了: public class Rect extends Shape JDK 1.5的泛型實現(Generics in JDK 1.5) 侯捷觀點 15 implements Serializable { Object m_left, m_top, m_width, m_height; public Rect(Object left, Object top, Object width, Object height ) { ... } ... } 這麼㆒來,任何數值運算,例如先前提過的「周長計算」L()將無法編譯,如圖 17: //class Rect 內 public double L() { return (double)((m_width + m_height) * 2); } 圖 17 / L()發生錯誤 錯誤訊息是:"operator + cannot be applied to T,T"。是的,兩個 Object object如何 相加呢?Java並沒有提供像 C++ 那樣的運算子重載(operator overloading)功能! 可以說,圖 2a的 Shape繼承體系只是介面正確,㆒旦面臨某些情況,卻無實用性。 我的結論是,將參數化型別用於 Java non-collection classes身㆖,恐怕會面臨許多 束縛。(註:讀者來函提供了此問題的㆒個解答,見本文末尾添加之補充) 更多資訊 以㆘是與本文主題相關的更多討論。這些資訊可以彌補本文篇幅限制而造成的不 足,並帶給您更多視野。 z 《Java 泛型技術之發展》,by 侯捷。http://www.jjhou.com/javatwo-2002-generics- in-jdk14.pdf z 《 Java 的物件永續之道》 ,by 侯捷。 http://www.jjhou.com/javatwo-2003- serialization-doc.pdf Generics in JDK 1.5 侯捷觀點 16 ■補充 讀者 AutoWay針對無法計算周長這個問題,來信如㆘: From: AutoWay Sent: Monday, January 17, 2005 7:57 PM Subject: 《JDK 1.5的泛型實現》讀者回應 侯捷兄:隨函寄㆖ Rect.java 程式之修訂,俾可以進行「周長計算」。修訂內容如 ㆘:在設定 type parameter時,同時宣告其 type bound,例如本例修改為 。系統進行編譯時,就知道 T 是 Number 或其 subclass;因此內部程式就 可運用 Number提供的 methods進行運算了。 我想,這就是 type parameters之所以提供 type bounds機制的主要原因。如果 type bounds光用來限制 type arguments之傳遞,實在沒啥意思!感謝本文揭示的例子, 讓我對 type bounds有進㆒步的省思與認識;若有謬誤,亦請來信指教。 侯捷回覆:非常感謝 AutoWay兄的指正,解除了我的盲點。整理於㆘。圖 6 之程 式碼應改為: public class Rect extends Shape implements Serializable { ... } public class Circle extends Shape implements Serializable { ... } public class Stroke extends Shape implements Serializable { ... } 圖 17程式碼應改為: //class Rect 內 public double L() { return (m_width.doubleValue() + m_height.doubleValue()) * 2; } 另兩個 classes(Circle和 Stroke)同理修改。
本文档为【JDK1.5的泛型实现】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_894033
暂无简介~
格式:pdf
大小:441KB
软件:PDF阅读器
页数:16
分类:互联网
上传时间:2011-07-23
浏览量:23