JAVA—咖啡館

          ——?dú)g迎訪問(wèn)rogerfan的博客,常來(lái)《JAVA——咖啡館》坐坐,喝杯濃香的咖啡,彼此探討一下JAVA技術(shù),交流工作經(jīng)驗(yàn),分享JAVA帶來(lái)的快樂(lè)!本網(wǎng)站部分轉(zhuǎn)載文章,如果有版權(quán)問(wèn)題請(qǐng)與我聯(lián)系。

          BlogJava 首頁(yè) 新隨筆 聯(lián)系 聚合 管理
            447 Posts :: 145 Stories :: 368 Comments :: 0 Trackbacks

          1         直觀印象

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

          對(duì)象加入其中(都被當(dāng)作Object實(shí)例看待);從容器中取出的對(duì)象也只是一個(gè)Object實(shí)例,需要將其強(qiáng)制轉(zhuǎn)型為期待的類(lèi)型,這種強(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); //編譯通過(guò),直到運(yùn)行時(shí)才拋ClassCastException

                

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

           

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

          intList.add(new Integer(0));

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

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

           

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

          2         Generic類(lèi)

          JDK1.5Collection類(lèi)庫(kù)的大部分類(lèi)都被改進(jìn)為Generic類(lèi)。以下是從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的類(lèi)型參數(shù),用戶(hù)在使用List

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

           

          public String demo() {

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

                 strList.add(“Hello!”);

                 return strList.get(0);

          }

           

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

           

          public String demo() {

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

                 strList.add(“Hello!”);

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

          }

           

                 可見(jiàn),類(lèi)型值信息只為Java編譯器在編譯時(shí)所用,確保代碼無(wú)類(lèi)型安全問(wèn)題;驗(yàn)證通過(guò)之后,即被去除。對(duì)于JVM而言,只有如JDK1.5之前版本一樣的List,并無(wú)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代碼類(lèi)型安全性(在編譯時(shí)確保,而非等到運(yùn)行時(shí)才暴露),Java代碼與Java編譯器之間新增的一種約定規(guī)范。Java編譯器在編譯結(jié)果*.class文件中供JVM讀取的部分里沒(méi)有保留Generic的任何信息;JVM看不到Generic的存在。

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

          1)        由于對(duì)于JVM而言,只有一個(gè)GenericClass類(lèi),所以GenericClass類(lèi)的靜態(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類(lèi)可以有多個(gè)類(lèi)型參數(shù),且類(lèi)型參數(shù)命名一般為大寫(xiě)單字符。例如Collection類(lèi)庫(kù)中的Map聲明為:

           

          public interface Map<K,V> {

                 ……;

          }

          3         Generic類(lèi)和原(Raw)類(lèi)

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

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

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

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

           

          public void test () {

              List list = new ArrayList();

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

          }

           

          4         Generic類(lèi)和子類(lèi)

          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的子類(lèi),因此‘Object o = “String”’合法一樣,存放StringList是存放ObjectList的子類(lèi),因此第二行應(yīng)該是合法的。反過(guò)來(lái)分析,如果第二行是合法的,那么如下會(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”編譯通過(guò)。

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

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

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

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

           

          String[] words = new String[10];

          Object[] objects = words;

          Objects[0] = new Object(); //編譯通過(guò),但運(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); //編譯通過(guò),運(yùn)行時(shí)拋ClassCastException

           

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

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

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

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

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

           

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

              //如此遍歷Collection的簡(jiǎn)潔方式也是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元素,例如如下代碼的第三行編譯不通過(guò):

           

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

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

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

          }

           

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

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

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

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

          7         Generic方法

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

           

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

                 for (T o : a) {

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

              }

          }

           

          posted on 2008-05-27 11:47 rogerfan 閱讀(349) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): 【Java知識(shí)】
          主站蜘蛛池模板: 宜春市| 阳西县| 关岭| 建德市| 册亨县| 衡阳县| 鄢陵县| 新泰市| 工布江达县| 农安县| 上栗县| 广水市| 久治县| 青龙| 门头沟区| 长阳| 宁河县| 阿克陶县| 陵川县| 昌吉市| 西乡县| 鹿邑县| 丰城市| 通渭县| 武陟县| 鄂托克旗| 平定县| 大竹县| 永定县| 台江县| 镇江市| 天全县| 准格尔旗| 壤塘县| 忻州市| 长海县| 沾化县| 商南县| 东光县| 上犹县| 南靖县|