Junky's IT Notebook

          統計

          留言簿(8)

          積分與排名

          WebSphere Studio

          閱讀排行榜

          評論排行榜

          JDK1.5新特性之Java Generics(轉)

          1         直觀印象

          JDK1.5之前的版本中,對于一個Collection類庫中的容器類實例,可將任意類型

          對象加入其中(都被當作Object實例看待);從容器中取出的對象也只是一個Object實例,需要將其強制轉型為期待的類型,這種強制轉型的運行時正確性由程序員自行保證。

          例如以下代碼片斷:

           

          List intList = new ArrayList(); //創建一個List,準備存放一些Integer實例

          intList.add(new Integer(0));

          intList.add(“1”); //不小心加入了一個字符串;但在編譯和運行時都不報錯,只有仔細的代碼走
                                 //才能揪出

          Integer i0 = (Integer)intList.get(0);

          Integer i1 = (Integer)intList.get(1); //編譯通過,直到運行時才拋ClassCastException

                

          而在JDK1.5中,可以創建一個明確只能存放某種特定類型對象的容器類實例,例如如下代碼:

           

          List<Integer> intList = new ArrayList<Integer>(); //intList為存放Integer實例的List

          intList.add(new Integer(0));

          Integer i0 = intList.get(0); //無需(Integer)強制轉型;List<Integer>get()返回的就是Integer
                                              //型對象

          intList.add(“1”); //編譯不通過,因為List<Integer>add()方法只接受Integer類型的參數

           

                 List<Integer> intList = new ArrayList<Integer>();”就是最簡單且最常用的Generic應用;顯然,運用Generic后的代碼更具可讀性和健壯性。

          2         Generic

          JDK1.5Collection類庫的大部分類都被改進為Generic類。以下是從JDK1.5源碼中

          截取的關于ListIterator接口定義的代碼片斷:

           

          public interface List<E> {

                 void add(E x);

                 Iterator<E> iterator;

          }

          public interface Iterator<E> {

                 E next();

                 boolean hasNext();

          }

           

          List為例,“public interface List<E>”中的EList的類型參數,用戶在使用List

          時可為類型參數指定一個確定類型值(如List<Integer>)。類型值為Java編譯器所用,確保用戶代碼類型安全。例如,編譯器知道List<Integer>add()方法只接受Integer類型的參數,因此如果你在代碼中將一個字符串傳入add()將導致編譯錯誤;編譯器知道Iterator<Integer>next()方法返回一個Integer的實例,你在代碼中也就無需對返回結果進行(Integer)強制轉型。代碼檢驗通過(語法正確且不會導致運行時類型安全問題)后,編譯器對現有代碼有一個轉換工作。簡單的說,就是去除代碼中的類型值信息,在必要處添加轉型代碼。例如如下代碼:

           

          public String demo() {

                 List<String> strList = new ArrayList<String>();

                 strList.add(“Hello!”);

                 return strList.get(0);

          }

           

          編譯器在檢驗通過后,將其轉換為:

           

          public String demo() {

                 List strList = new ArrayList(); //去除類型值<String>

                 strList.add(“Hello!”);

                 return (String)strList.get(0);  //添加轉型動作代碼(String)

          }

           

                 可見,類型值信息只為Java編譯器在編譯時所用,確保代碼無類型安全問題;驗證通過之后,即被去除。對于JVM而言,只有如JDK1.5之前版本一樣的List,并無List<Integer>List<String>之分。這也就是Java Generics實現中關鍵技術Erasure的基本思想。以下代碼在控制臺輸出的就是“true”。

           

          List<String> strList = new ArrayList<String>();

          List<Integer> intList = new ArrayList<Integer>();

          System.out.println(strList.getClass() == intList.getClass());

           

                 可以將Generic理解為:為提高Java代碼類型安全性(在編譯時確保,而非等到運行時才暴露),Java代碼與Java編譯器之間新增的一種約定規范。Java編譯器在編譯結果*.class文件中供JVM讀取的部分里沒有保留Generic的任何信息;JVM看不到Generic的存在。

                 對于Generic類(設為GenericClass)的類型參數(設為T):

          1)        由于對于JVM而言,只有一個GenericClass類,所以GenericClass類的靜態字段和靜態方法的定義中不能使用TT只能出現在GenericClass的非靜態字段或非靜態方法中。也即T是與GenericClass的實例相關的信息;

          2)        T只在編譯時被編譯器理解,因此也就不能與運行時被JVM理解并執行其代表的操作的操作符(如instanceof new)聯用。

           

          class GenericClass<T> {

              T t1;

              public void method1(T t){

                 t1 = new T(); //編譯錯誤,T不能與new聯用

                 if (t1 instanceof T) {}; //編譯錯誤,T不能與instanceof聯用

              };

              static T t2; //編譯錯誤,靜態字段不能使用T

              public static void method2(T t){};//編譯錯誤,靜態方法不能使用T

          }

           

                 Generic類可以有多個類型參數,且類型參數命名一般為大寫單字符。例如Collection類庫中的Map聲明為:

           

          public interface Map<K,V> {

                 ……;

          }

          3         Generic類和原(Raw)類

          對每一個Generic類,用戶在使用時可以不指定類型參數。例如,對于List<E>,用戶

          可以以“List<String> list;”方式使用,也可以以“List list;”方式使用。“List<String>”被稱為參數化的Generic類(類型參數被賦值),而“List”稱為原類。原類List的使用方式和效果與JDK1.5之前版本List的一樣;使用原類也就失去了Generic帶來的可讀性和健壯性的增強。

                 允許原類使用方式的存在顯然是為了代碼的向前兼容:即JDK1.5之前的代碼在JDK1.5下仍然編譯通過且正常運行。

                 當你在JDK1.5中使用原類并向原類實例中添加對象時,編譯器會產生警告,因為它無法保證待添加對象類型的正確性。編譯通過是為了保證代碼向前兼容,產生警告是提醒潛在的風險。

           

          public void test () {

              List list = new ArrayList();

              list.add("tt");//JDK1.5編譯器對此行產生警告

          }

           

          4         Generic類和子類

          List<String> ls = new ArrayList<String>();

          List<Object> lo = ls; //編譯錯誤:Type mismatch: cannot convert from List<Dummy> to 
                                                                     //List<Object>

           

          以上第二行代碼導致的編譯錯誤“Type mismatch: cannot convert from List<Dummy> to

          List<Object>”是不是有點出人意料?直觀上看,就像StringObject的子類,因此‘Object o = “String”’合法一樣,存放StringList是存放ObjectList的子類,因此第二行應該是合法的。反過來分析,如果第二行是合法的,那么如下會導致運行時異常的代碼也是合法的:

           

          lo.add(new Object); //會導致在ls中添加了非String對象

          String s = ls.get(0); //ls.get(0)返回的實際上只是一個Object實例,會導致ClassCastException

           

                 編譯器顯然不允許此種情形發生,因此不允許“List<Object> lo = ls”編譯通過。

                 因此,直觀上的“存放StringList是存放ObjectList的子類”是錯誤的。更一般的說,設FooBar的子類,GGeneric類型聲明,G<Foo>不是G<Bar>的子類。

          5         參數化的Generic類和數組

          我們知道,如果TS的子類,則T[]也是S[]的子類。因此,如下代碼編譯通過,只

          在運行時于第三行代碼處拋ArrayStoreException

           

          String[] words = new String[10];

          Object[] objects = words;

          Objects[0] = new Object(); //編譯通過,但運行時會拋ArrayStoreException

           

          再分析如下代碼:

           

          List<String>[] wordLists = new ArrayList<String>[10];

          ArrayList<Integer> integerList = new ArrayList<Integer>();

          integerList.add(123);

          Object[] objects = wordLists;

          objects[0] = integerList;//運行時不出錯,因為運行時ArrayList<String>ArrayList<Integer>
                                           //
          ArrayList

          String s = wordlists[0].get(0); //編譯通過,運行時拋ClassCastException

           

                 就出現了“正確使用了Generic,但在運行時仍然出現ClassCastException”的情形。顯然Java編譯器不允許此種情形的發生。事實上,以上代碼的第一行“List<String>[] wordLists = new ArrayList<String>[10];”就是編譯不通過的,也就不存在接下來的代碼。

          更一般地說,不能創建參數化的Generic類的數組。

          6         類型參數通配符?

          由“Generic類和子類”節知,Collection<Object>不是存放其它類型對象的Collection(例

          Collection<String>)的基類(抽象),那么如何表示任一種參數化的Collection的呢?使用Collection<?>。?即代表任一類型參數值。例如,我們可以很容易寫出下面的通用函數printCollection()

           

          public static void printCollection(Collection<?> c) {

              //如此遍歷Collection的簡潔方式也是JDK1.5新引入的

                 for (Object o : c) {

                        System.out.println(o);

              }

          }

           

                 這樣,既可以將Collection<String>的實例,也可以將Collection<Integer>的實例作為參數調用printCollection()方法。

                 然而,要注意一點,你不能往Collection<?>容器實例中加入任何非null元素,例如如下代碼的第三行編譯不通過:

           

          public static void testAdd(Collection<?> c) {

                 c.add(null); //編譯通過

                 c.add(“test”); //編譯錯誤

          }

           

                 很好理解:c中要存放的對象的具體類型不確定,編譯器無法驗證待添加對象類型的正確性,因此不能加入對象實例;而null可以看作是任一個類的實例,因而允許加入。

                 另外,盡管c中要存放的對象的類型不確定,但我們知道任何類都是Object子類,因此從c中取出的對象都統一作為Object實例。

                 更進一步,如果BaseClass代表某個可被繼承的類的類名,那么Collection<? extends BaseClass>代表類型參數值為BaseClassBaseClass某個子類的任一參數化Collection。對于Collection<? extends BaseClass>的實例c,因為c中要存放的對象具體類型不確定,不能往其加入非null對象,但從c中取出的對象都統一作為BaseClass實例。事實上,你可以把Collection<?>看作Collection<? extends Object>的簡潔形式。

                 另一種情形:如果SubClass代表任一個類的類名,那么Collection<? super SubClass>代表類型參數值為SubClassSubClass某個祖先類的任一參數化Collection。對于Collection<? super SubClass>的實例c,你可以將SubClass實例加入其中,但從中取出的對象都是Object實例。

          7         Generic方法

          我們可以定義Generic類,同樣可以定義Generic方法,即將方法的一個或多個參數的類型參數化,如代碼:

           

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

                 for (T o : a) {

                     c.add(o); //合法。注意與Collection<?>的區別

              }

          }

           

                 我們可以以如下方式調用fromArrayToCollection()

           

          Object[] oa = new Object[100];

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

          fromArrayToCollection(oa, co); //此時,T即為Object

           

          String[] sa = new String[100];

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

          fromArrayToCollection(sa, cs); //此時,T即為String

          fromArrayToCollection(sa, co); //此時,T即為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即為Number

          fromArrayToCollection(fa, cn); //此時,T即為Number

          fromArrayToCollection(na, cn); //此時,T即為Number

          fromArrayToCollection(na, co); //此時,T即為Object

          fromArrayToCollection(na, cs); //編譯錯誤

           

                 通過以上代碼可以看出,我們在調用fromArrayToCollection()時,無需明確指定T為何種類型(與Generic類的使用方式不同),而是像調用一般method一樣,直接提供參數值,編譯器會根據提供的參數值自動為T賦類型值或提示編譯錯誤(參數值不當)。

                 考慮如下函數sum()

           

          public static long sum(Collection<? extends Number> numbers) {

              long sum = 0;

              for (Number n : numbers) {

                 sum += n.longValue();

              }

              return sum;

          }

           

                 我們也可以將其以Generic方法實現:

           

          public static <T extends Number> long sum(Collection<T> numbers) {

              long sum = 0;

              for (Number n : numbers) {

                 sum += n.longValue();

              }

              return sum;

          }

           

                 那么對于一個方法,當要求參數類型可變時,是采用Generic方法,還是采用類型參數通配符方式呢?一般而言,如果參數類型間或參數類型與返回值類型間存在某種依賴關系,則采取Generic方法,否則采取類型參數通配符方式。

                 這一原則在Collection類庫的源代碼中得到了很好的體現,例如Collection接口的containsAll()addAll()toArray()方法:

           

          interface Collection<E> {

                 public boolean containsAll(Collecion<?> c); //參數間類型以及參數與返回
                                                                              //值間類型無依賴

                 <T> T[] toArray(T[] a); //參數a與返回值都是相同類的數組,有依賴

          }

           

                 當然,根據需要,二者也可以結合使用,例如Collections中的copy()方法:

                

          class Collections {

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

                     …….

                 }

          }

          posted on 2007-05-08 14:03 junky 閱讀(614) 評論(1)  編輯  收藏 所屬分類: java

          評論

          # re: JDK1.5新特性之Java Generics(轉) 2008-05-20 14:47 Roger Tu

          該文首發在http://www.aygfsteel.com/rogertu,后來轉移到http://hi.baidu.com/rogertu的, 請尊重別人的勞動成果,表明作者和出處,謝謝!  回復  更多評論   

          主站蜘蛛池模板: 察哈| 遵义市| 卢龙县| 洛隆县| 牡丹江市| 溆浦县| 汕尾市| 枣庄市| 铅山县| 射阳县| 东城区| 离岛区| 光山县| 长治市| 鸡泽县| 长阳| 盐津县| 台州市| 桂阳县| 永清县| 利川市| 达尔| 凌海市| 防城港市| 东山县| 澄迈县| 平罗县| 江源县| 通榆县| 云霄县| 法库县| 泰安市| 龙游县| 高密市| 乌鲁木齐县| 曲靖市| 齐河县| 枣庄市| 晋中市| 呼图壁县| 石阡县|