<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Junky's IT Notebook

    統(tǒng)計(jì)

    留言簿(8)

    積分與排名

    WebSphere Studio

    閱讀排行榜

    評(píng)論排行榜

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

    1         直觀印象

    JDK1.5之前的版本中,對(duì)于一個(gè)Collection類庫中的容器類實(shí)例,可將任意類型

    對(duì)象加入其中(都被當(dāng)作Object實(shí)例看待);從容器中取出的對(duì)象也只是一個(gè)Object實(shí)例,需要將其強(qiáng)制轉(zhuǎn)型為期待的類型,這種強(qiáng)制轉(zhuǎn)型的運(yùn)行時(shí)正確性由程序員自行保證。

    例如以下代碼片斷:

     

    List intList = new ArrayList(); //創(chuàng)建一個(gè)List,準(zhǔn)備存放一些Integer實(shí)例

    intList.add(new Integer(0));

    intList.add(“1”); //不小心加入了一個(gè)字符串;但在編譯和運(yùn)行時(shí)都不報(bào)錯(cuò),只有仔細(xì)的代碼走
                           //才能揪出

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

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

          

    而在JDK1.5中,可以創(chuàng)建一個(gè)明確只能存放某種特定類型對(duì)象的容器類實(shí)例,例如如下代碼:

     

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

    intList.add(new Integer(0));

    Integer i0 = intList.get(0); //無需(Integer)強(qiáng)制轉(zhuǎn)型;List<Integer>get()返回的就是Integer
                                        //型對(duì)象

    intList.add(“1”); //編譯不通過,因?yàn)?/span>List<Integer>add()方法只接受Integer類型的參數(shù)

     

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

    2         Generic

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

    截取的關(guān)于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的類型參數(shù),用戶在使用List

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

     

    public String demo() {

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

           strList.add(“Hello!”);

           return strList.get(0);

    }

     

    編譯器在檢驗(yàn)通過后,將其轉(zhuǎn)換為:

     

    public String demo() {

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

           strList.add(“Hello!”);

           return (String)strList.get(0);  //添加轉(zhuǎn)型動(dòng)作代碼(String)

    }

     

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

     

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

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

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

     

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

           對(duì)于Generic類(設(shè)為GenericClass)的類型參數(shù)(設(shè)為T):

    1)        由于對(duì)于JVM而言,只有一個(gè)GenericClass類,所以GenericClass類的靜態(tài)字段和靜態(tài)方法的定義中不能使用TT只能出現(xiàn)在GenericClass的非靜態(tài)字段或非靜態(tài)方法中。也即T是與GenericClass的實(shí)例相關(guān)的信息;

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

     

    class GenericClass<T> {

        T t1;

        public void method1(T t){

           t1 = new T(); //編譯錯(cuò)誤,T不能與new聯(lián)用

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

        };

        static T t2; //編譯錯(cuò)誤,靜態(tài)字段不能使用T

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

    }

     

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

     

    public interface Map<K,V> {

           ……;

    }

    3         Generic類和原(Raw)類

    對(duì)每一個(gè)Generic類,用戶在使用時(shí)可以不指定類型參數(shù)。例如,對(duì)于List<E>,用戶

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

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

           當(dāng)你在JDK1.5中使用原類并向原類實(shí)例中添加對(duì)象時(shí),編譯器會(huì)產(chǎn)生警告,因?yàn)樗鼰o法保證待添加對(duì)象類型的正確性。編譯通過是為了保證代碼向前兼容,產(chǎn)生警告是提醒潛在的風(fēng)險(xiǎn)。

     

    public void test () {

        List list = new ArrayList();

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

    }

     

    4         Generic類和子類

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

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

     

    以上第二行代碼導(dǎo)致的編譯錯(cuò)誤“Type mismatch: cannot convert from List<Dummy> to

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

     

    lo.add(new Object); //會(huì)導(dǎo)致在ls中添加了非String對(duì)象

    String s = ls.get(0); //ls.get(0)返回的實(shí)際上只是一個(gè)Object實(shí)例,會(huì)導(dǎo)致ClassCastException

     

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

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

    5         參數(shù)化的Generic類和數(shù)組

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

    在運(yùn)行時(shí)于第三行代碼處拋ArrayStoreException

     

    String[] words = new String[10];

    Object[] objects = words;

    Objects[0] = new Object(); //編譯通過,但運(yùn)行時(shí)會(huì)拋ArrayStoreException

     

    再分析如下代碼:

     

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

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

    integerList.add(123);

    Object[] objects = wordLists;

    objects[0] = integerList;//運(yùn)行時(shí)不出錯(cuò),因?yàn)檫\(yùn)行時(shí)ArrayList<String>ArrayList<Integer>
                                     //
    ArrayList

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

     

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

    更一般地說,不能創(chuàng)建參數(shù)化的Generic類的數(shù)組。

    6         類型參數(shù)通配符?

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

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

     

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

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

           for (Object o : c) {

                  System.out.println(o);

        }

    }

     

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

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

     

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

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

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

    }

     

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

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

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

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

    7         Generic方法

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

     

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

           for (T o : a) {

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

        }

    }

     

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

     

    Object[] oa = new Object[100];

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

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

     

    String[] sa = new String[100];

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

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

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

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

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

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

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

     

           通過以上代碼可以看出,我們?cè)谡{(diào)用fromArrayToCollection()時(shí),無需明確指定T為何種類型(與Generic類的使用方式不同),而是像調(diào)用一般method一樣,直接提供參數(shù)值,編譯器會(huì)根據(jù)提供的參數(shù)值自動(dòng)為T賦類型值或提示編譯錯(cuò)誤(參數(shù)值不當(dāng))。

           考慮如下函數(shù)sum()

     

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

        long sum = 0;

        for (Number n : numbers) {

           sum += n.longValue();

        }

        return sum;

    }

     

           我們也可以將其以Generic方法實(shí)現(xiàn):

     

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

        long sum = 0;

        for (Number n : numbers) {

           sum += n.longValue();

        }

        return sum;

    }

     

           那么對(duì)于一個(gè)方法,當(dāng)要求參數(shù)類型可變時(shí),是采用Generic方法,還是采用類型參數(shù)通配符方式呢?一般而言,如果參數(shù)類型間或參數(shù)類型與返回值類型間存在某種依賴關(guān)系,則采取Generic方法,否則采取類型參數(shù)通配符方式。

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

     

    interface Collection<E> {

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

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

    }

     

           當(dāng)然,根據(jù)需要,二者也可以結(jié)合使用,例如Collections中的copy()方法:

          

    class Collections {

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

               …….

           }

    }

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

    評(píng)論

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

    該文首發(fā)在http://www.tkk7.com/rogertu,后來轉(zhuǎn)移到http://hi.baidu.com/rogertu的, 請(qǐng)尊重別人的勞動(dòng)成果,表明作者和出處,謝謝!  回復(fù)  更多評(píng)論   

    主站蜘蛛池模板: 男女免费观看在线爽爽爽视频| 野花高清在线观看免费3中文 | 国产精品免费福利久久| 中文字幕手机在线免费看电影| 国产精品黄页在线播放免费| 亚洲中文精品久久久久久不卡| 无码AV动漫精品一区二区免费| 国产成人免费a在线视频app| 国产精品亚洲专区无码唯爱网| 搡女人真爽免费视频大全| 亚洲一卡2卡4卡5卡6卡残暴在线| 99久久国产热无码精品免费| 亚洲乱码一区av春药高潮| 免费观看成人毛片a片2008| 亚洲卡一卡二卡乱码新区| 成人免费777777| 亚洲av日韩专区在线观看| 国产成人免费a在线视频app| 一级做a爰全过程免费视频毛片| 亚洲精品国产高清嫩草影院| 国产精品亚洲一区二区三区| 一级毛片在线观看免费| 亚洲今日精彩视频| 国产免费的野战视频| 亚洲精品色播一区二区| 污污网站免费观看| 亚洲性一级理论片在线观看| 在线观看特色大片免费视频| 美女18毛片免费视频| 亚洲日韩精品一区二区三区| 四虎免费影院ww4164h| 国产亚洲3p无码一区二区| 香蕉成人免费看片视频app下载| 亚洲午夜电影一区二区三区| 国产精品无码免费视频二三区| 黄色三级三级免费看| 亚洲AV第一页国产精品| 四虎影视免费在线| 搡女人免费免费视频观看| 亚洲五月综合缴情婷婷| MM131亚洲国产美女久久|