隨筆 - 117  文章 - 72  trackbacks - 0

          聲明:原創(chuàng)作品(標(biāo)有[原]字樣)轉(zhuǎn)載時(shí)請(qǐng)注明出處,謝謝。

          常用鏈接

          常用設(shè)置
          常用軟件
          常用命令
           

          訂閱

          訂閱

          留言簿(7)

          隨筆分類(130)

          隨筆檔案(123)

          搜索

          •  

          積分與排名

          • 積分 - 156001
          • 排名 - 389

          最新評(píng)論

          (轉(zhuǎn)載自:[url]http://blog.csdn.net/andycpp/archive/2007/08/17/1748731.aspx[/url])
          寫(xiě)的很好,保存一下.

              從jdk1.5開(kāi)始,Java中開(kāi)始支持范型了。范型是一個(gè)很有用的編程工具,給我們帶來(lái)了極大的靈活性。在看了《java核心編程》之后,我小有收獲,寫(xiě)出來(lái)與大家分享。
              所謂范型,我的感覺(jué)就是,不用考慮對(duì)象的具體類型,就可以對(duì)對(duì)象進(jìn)行一定的操作,對(duì)任何對(duì)象都能進(jìn)行同樣的操作。這就是靈活性之所在。但是,正是因?yàn)闆](méi)有 考慮對(duì)象的具體類型,因此一般情況下不可以使用對(duì)象自帶的接口函數(shù),因?yàn)椴煌膶?duì)象所攜帶的接口函數(shù)不一樣,你使用了對(duì)象A的接口函數(shù),萬(wàn)一別人將一個(gè)對(duì) 象B傳給范型,那么程序就會(huì)出現(xiàn)錯(cuò)誤,這就是范型的局限性。所以說(shuō),范型的最佳用途,就是用于實(shí)現(xiàn)容器類,實(shí)現(xiàn)一個(gè)通用的容器。該容器可以存儲(chǔ)對(duì)象,也可 以取出對(duì)象,而不用考慮對(duì)象的具體類型。因此,在學(xué)習(xí)范型的時(shí)候,一定要了解這一點(diǎn),你不能指望范型是萬(wàn)能的,要充分考慮到范型的局限性。下面我們來(lái)探討 一下范型的原理以及高級(jí)應(yīng)用。首先給出一個(gè)范型類:
          public class Pair<T> 

             
          public Pair() { first = null; second = null; } 
             
          public Pair(T first, T second) this.first = first;  this.second = second; } 
           
             
          public T getFirst() return first; } 
             
          public T getSecond() return second; } 
           
             
          public void setFirst(T newValue) { first = newValue; } 
             
          public void setSecond(T newValue) { second = newValue; } 
           
             
          private T first; 
             
          private T second; 
          }
           

                  我們看到,上述Pair類是一個(gè)容器類(我會(huì)多次強(qiáng)調(diào),范型天生就是為了容器類的方便實(shí)現(xiàn)),容納了2個(gè)數(shù)據(jù),但這2個(gè)數(shù)據(jù)類型是不確定的,用范型T來(lái)表示。關(guān)于范型類如何使用,那是最基本的內(nèi)容,在此就不討論了。
                  下面我們來(lái)討論一下Java中范型類的實(shí)現(xiàn)原理。在java中,范型是在編譯器中實(shí)現(xiàn)的,而不是在虛擬機(jī)中實(shí)現(xiàn)的,虛擬機(jī)對(duì)范型一無(wú)所知。因此,編譯器一 定要把范型類修改為普通類,才能夠在虛擬機(jī)中執(zhí)行。在java中,這種技術(shù)稱之為“擦除”,也就是用Object類型替換范型。上述代碼經(jīng)過(guò)擦除后就變成 如下形式:
          public class Pair 

             
          public Pair(Object first, Object second) 
             

                
          this.first = first; 
                
          this.second = second; 
             }
           
           
             
          public Object getFirst() return first; } 
             
          public Object getSecond() return second; } 
           
             
          public void setFirst(Object newValue) { first = newValue; } 
             
          public void setSecond(Object newValue) { second = newValue; } 
           
             
          private Object first; 
             
          private Object second; 
          }
           

                  大家可以看到,這是一個(gè)普通類,所有的范型都被替換為Object類型,他被稱之為原生類。每當(dāng)你用一個(gè)具體類去實(shí)例化該范型時(shí),編譯器都會(huì)在原生類的基礎(chǔ)上,通過(guò)強(qiáng)制約束在需要的地方添加強(qiáng)制轉(zhuǎn)換代碼來(lái)滿足需求,但是不會(huì)生成更多的具體的類(這一點(diǎn)和c++完全不同)。我們來(lái)舉例說(shuō)明這一點(diǎn):
          Pair<Employee>  buddies  =  new  Pair<Employee>(); 

          //在上述原生代碼中,此處參數(shù)類型是Object,理論上可以接納各種類型,但編譯器通過(guò)強(qiáng)制約束
          //你只能在此使用Employee(及子類)類型的參數(shù),其他類型編譯器一律報(bào)錯(cuò)
          buddies.setFirst(new Employee("張三")); 

          //在上述原生代碼中,getFirst()的返回值是一個(gè)Object類型,是不可以直接賦給類型為Employee的buddy的
          //但編譯器在此做了手腳,添加了強(qiáng)制轉(zhuǎn)化代碼,實(shí)際代碼應(yīng)該是Employee buddy = (Employee)buddies.getFirst();
          //這樣就合法了。但編譯器做過(guò)手腳的代碼你是看不到的,他是以字節(jié)碼的形式完成的。
          Employee buddy = buddies.getFirst();

                  下面我們?cè)賮?lái)考察一個(gè)更復(fù)雜的情況,如果我們的Pair類要保證第二個(gè)屬性一定要大于第一個(gè)屬性,該如何做?這就涉及到兩個(gè)屬性的比較,但是這2個(gè)屬性類 型未知,可以比較嗎?我們前面也講過(guò),一般情況下不要涉及類型的具體信息。但是現(xiàn)在要比較2個(gè)屬性,不得不涉及類型的具體信息了。Java還是考慮到了這 一點(diǎn),那就是,范型類可以繼承自某一個(gè)父類,或者實(shí)現(xiàn)某個(gè)接口,或者同時(shí)繼承父類并且實(shí)現(xiàn)接口。這樣的話,就可以對(duì)類型調(diào)用父類或接口中定義的方法了。代 碼如下:
          public class Pair<T extends Comparable> 

             
          public boolean setSecond(T newValue) 
             boolean flag 
          = false;
             If(newValue.compareTo(first)
          >0{
               second 
          = newValue;
               flag 
          = true;
             }

             
          return flag;
          }
           
           
             
          private T first; 
             
          private T second; 
          }
           

                  我們看到,上面的范型T被我們添加了一個(gè)約束條件,那就是他必須實(shí)現(xiàn)Comparable接口,這樣的話,我們就可以對(duì)范型T使用接口中定義的方法了,也 就可以實(shí)現(xiàn)2個(gè)元素大小的比較。有人可能要問(wèn)了,實(shí)現(xiàn)一個(gè)接口不是用implements嗎?上面怎么用extends呢??為了簡(jiǎn)化范型的設(shè)計(jì),無(wú)論是 繼承類還是實(shí)現(xiàn)接口,一律使用extends關(guān)鍵字。這是規(guī)定,沒(méi)辦法,記住就行了。若同時(shí)添加多個(gè)約束,各個(gè)約束之間用“&”分隔,比 如:public class Pair<T extends Comparable & Serializable>。那么編譯器是如何處理這種情況呢?前面講過(guò),范型類最終都會(huì)被轉(zhuǎn)化為原生類。在前面沒(méi)有添加約束的時(shí)候,編譯器將范型 通通替換為Object;而增加了約束之后,通通用第一個(gè)約束來(lái)替換范型(上面的代碼就會(huì)用
          Comparable來(lái)替換所有范型),當(dāng)需要用到其他約束中定義的方法的時(shí)候,通過(guò)插入強(qiáng)制轉(zhuǎn)化代碼來(lái)實(shí)現(xiàn)。在此就不給出具體的例子了。
                  下面我們來(lái)看看最后一個(gè)知識(shí)點(diǎn),定義一個(gè)函數(shù),該函數(shù)接受一個(gè)范型類作為參數(shù)。首先讓我們來(lái)看一個(gè)最簡(jiǎn)單的情況,參數(shù)是一個(gè)實(shí)例化的范型類:
              public static void test(ArrayList<Number> l) {
                  l.add(
          new Integer(2));
              }

                  上述代碼中,形參list的元素被實(shí)例化為Number類型。在使用該函數(shù)的時(shí)候我們能不能傳入一個(gè)元素為Integer的list呢?看看下面代碼合法嗎?
              ArrayList<Integer> l = new ArrayList<Integer>();
              test(l);  
          //此處編譯器會(huì)報(bào)錯(cuò)!!

                  答案上面已經(jīng)給出了:不行!對(duì)于這種形參,實(shí)參的類型必須和他完全一致,即也應(yīng)該是一個(gè)元素為Number的list才可以,其他的實(shí)參一律不行。這是為 什么呢?Integer不是Number的子類嗎?子類的對(duì)象傳遞給父類的引用,不可以嗎?這里我們就要注意了,Integer確實(shí)是Number的子類,但是,ArrayList<Integer>并不是ArrayList<Number>的子類,二者之間沒(méi)有任何的繼承關(guān)系!!因此這樣傳遞參數(shù)是不允許的。如果允許的話,會(huì)出現(xiàn)什么問(wèn)題嗎?當(dāng)然會(huì),我們對(duì)test函數(shù)重新定義一下:
              public static void test(ArrayList<Number> l) {
                  l.add(
          new Float(2));
              }

                  大家可以看到,在函數(shù)內(nèi)部,我們把Float類型的元素插入到鏈表中。因?yàn)殒湵硎荖umber類型,這條語(yǔ)句沒(méi)問(wèn)題。但是,如果實(shí)參是一個(gè)Integer 類型的鏈表,他能存儲(chǔ)Float類型的數(shù)據(jù)嗎??顯然不能,這樣就會(huì)造成運(yùn)行時(shí)錯(cuò)誤。于是,編譯器干脆就不允許進(jìn)行這樣的傳遞。
                  通過(guò)分析我們看到,出錯(cuò)的可能性只有一個(gè):在向容器類添加內(nèi) 容的時(shí)候可能造成類型不匹配。那么有些人可能會(huì)有這種要求:“我保證一定不對(duì)容器添加內(nèi)容,我非常希望能夠?qū)⒁粋€(gè)Integer類(Number類的子 類)組成的鏈表傳遞進(jìn)來(lái)”。Sun的那幫大牛們當(dāng)然會(huì)考慮到這種訴求,這樣的功能是可以實(shí)現(xiàn)的,并且還有兩種方式呢,看下面代碼:
          //     1.在定義方法的時(shí)候使用Wildcard(也就是下述代碼中的問(wèn)號(hào))。
              public static void test1(ArrayList<? extends Number> l) {
                  Integer n 
          = new Integer(45);
                  Number x 
          = l.get(0); //從鏈表中取數(shù)據(jù)是允許的
                  l.add(n);  //錯(cuò)誤!!往鏈表里面插入數(shù)據(jù)是被編譯器嚴(yán)格禁止的!!
              }


          //     2.定義一個(gè)范型方法。代碼如下:
              public static <extends Number> void test2(ArrayList<T> l) {
                  Number n 
          = l.get(0);
                  T d 
          = l.get(0);
                  l.add(d);  
          //與上面的方法相比,插入一個(gè)范型數(shù)據(jù)是被允許的,相對(duì)靈活一些
                  l.add(n);  //錯(cuò)誤!!只可以插入范型數(shù)據(jù),絕不可插入具體類型數(shù)據(jù)。
              }

                  按照上述代碼的寫(xiě)法,只要我們對(duì)形參添加了一定的約束條件,那么我們?cè)趥鬟f實(shí)參的時(shí)候,對(duì)實(shí)參的嚴(yán)格約束就會(huì)降低一些。上述代碼都指定了一個(gè)類 Number,并用了extends關(guān)鍵字,因此,在傳遞實(shí)參的時(shí)候,凡是從Number繼承的類組成的鏈表,均可以傳遞進(jìn)去。但上面代碼的注釋中也說(shuō)的 很清楚,為了不出現(xiàn)運(yùn)行時(shí)錯(cuò)誤,編譯器會(huì)對(duì)你調(diào)用的方法做嚴(yán)格的限制:凡是參數(shù)為范型的方法,一律不需調(diào)用!!
          l.get(0)是合法的,因?yàn)閰?shù)是整型而不是范型;l.add(x)就不合法,因?yàn)閍dd函數(shù)的參數(shù)是范型。但是定義一個(gè)范型方法還是有一定靈活性的,如果傳入的數(shù)據(jù)也是范型,編譯器還是認(rèn)可的,因?yàn)榉缎蛯?duì)范型,類型安全是可以保證的。
                  從上述代碼可以看出,定義一個(gè)范型方法要比Wildcard稍微靈活一些,可以往鏈表中添加T類型的對(duì)象,而Wildcard中是不允許往鏈表中添加任何類型的對(duì)象的。那么我們還要Wildcard干什么呢?
          Wildcard還是有他存在的意義的,那就是,Wildcard支持另外一個(gè)關(guān)鍵字super,而范型方法不支持super關(guān)鍵字。換句話說(shuō),如果你要實(shí)現(xiàn)這樣的功能:“傳入的參數(shù)應(yīng)該是指定類的父類”,范型方法就無(wú)能為力了,只能依靠Wildcard來(lái)實(shí)現(xiàn)。代碼如下:
              public static void test5(ArrayList<? super Integer> l) {
                  Integer n 
          = new Integer(45);
                  l.add(n);  
          //與上面使用extends關(guān)鍵字相反,往鏈表里面插入指定類型的數(shù)據(jù)是被允許的。
                  Object x = l.get(0); //從鏈表里取出一個(gè)數(shù)據(jù)仍然是被允許的,不過(guò)要賦值給Object對(duì)象。
                  l.add(x);   //錯(cuò)誤!!將剛剛?cè)〕龅臄?shù)據(jù)再次插入鏈表是不被允許的。
              }
                  這種實(shí)現(xiàn)方式的特點(diǎn)我們前面已經(jīng)說(shuō)過(guò)了,就是對(duì)實(shí)參的限制更改為:必須是指定類型的父類。這里我們指定了Integer類,那么實(shí)參鏈表的元素類型,必須 是Number類及其父類。下面我們重點(diǎn)討論一下上述代碼的第四條語(yǔ)句,為什么將剛剛?cè)〕龅臄?shù)據(jù)再次插入鏈表不被允許??道理很簡(jiǎn)單,剛剛?cè)〕龅臄?shù)據(jù)被保 存在一個(gè)Object類型的引用中,而鏈表的add方法只能接受指定類型Integer及其子類,類型不匹配當(dāng)然不行。有些人可能立刻會(huì)說(shuō),我將他強(qiáng)制轉(zhuǎn) 化為Integer類(即
          l.add((Integer)x); , 編譯器不就不報(bào)錯(cuò)了嗎?確實(shí),經(jīng)過(guò)強(qiáng)制轉(zhuǎn)化后,編譯器確實(shí)沒(méi)意見(jiàn)了。不過(guò)這種強(qiáng)制轉(zhuǎn)化有可能帶來(lái)運(yùn)行時(shí)錯(cuò)誤。因?yàn)槟銈魅氲膶?shí)參,其元素類型是 Integer的父類,比如是Number。那么,存儲(chǔ)在該鏈表中的第一個(gè)數(shù)據(jù),很有可能是Double或其他類型的,這是合法的。那么你取出的第一個(gè)元 素x也會(huì)是Double類型。那么你把一個(gè)Double類型強(qiáng)制轉(zhuǎn)化為Integer類型,顯然是一個(gè)運(yùn)行時(shí)錯(cuò)誤。
                  難道“把取出的元素再插入到鏈表中”這樣一個(gè)功能就實(shí)現(xiàn)不了嗎?當(dāng)然可以,不過(guò)不能直接實(shí)現(xiàn),要借助范型函數(shù)的幫忙,因?yàn)樵诜缎秃瘮?shù)中,剛剛?cè)〕龅脑卦俅婊厝ナ遣怀蓡?wèn)題的。定義這樣一個(gè)范型函數(shù),我們稱之為幫助函數(shù)。代碼如下:
              //幫助函數(shù)
              public static <T>void helperTest5(ArrayList<T> l, int index) {
                  T temp 
          = l.get(index);
                  l.add(temp);
              }

              
              
          //主功能函數(shù)
              public static void test5(ArrayList<? super Integer> l) {
                  Integer n 
          = new Integer(45);
                  l.add(n);  
                  helperTest5(l, 
          0);   //通過(guò)幫助類,將指定的元素取出后再插回去。
              }

                  上述兩個(gè)函數(shù)結(jié)合的原理就是:利用Wildcard的super關(guān)鍵字來(lái)限制參數(shù)的類型(范型函數(shù)不支持super,要是支持的話就不用這么麻煩了),然后通過(guò)范型函數(shù)來(lái)完成取出數(shù)據(jù)的再存儲(chǔ)。
                  以上就是我學(xué)習(xí)范型的所有心得。下面再把《Java核心編程》中列出的使用范型時(shí)的注意事項(xiàng)列出來(lái)(各種操作被禁止的原因就不具體說(shuō)明了),供大家參考:
          //1、不可以用一個(gè)本地類型(如int   float)來(lái)替換范型
          //2、運(yùn)行時(shí)類型檢查,不同類型的范型類是等價(jià)的(Pair<String>與Pair<Employee>是屬于同一個(gè)類型Pair),
          //     這一點(diǎn)要特別注意,即如果a instanceof Pair<String>==true的話,并不代表a.getFirst()的返回值是一個(gè)String類型
          //3、范型類不可以繼承Exception類,即范型類不可以作為異常被拋出
          //4、不可以定義范型數(shù)組
          //5、不可以用范型構(gòu)造對(duì)象,即first = new T(); 是錯(cuò)誤的
          //6、在static方法中不可以使用范型,范型變量也不可以用static關(guān)鍵字來(lái)修飾
          //7、不要在范型類中定義equals(T x)這類方法,因?yàn)镺bject類中也有equals方法,當(dāng)范型類被擦除后,這兩個(gè)方法會(huì)沖突
          //8、根據(jù)同一個(gè)范型類衍生出來(lái)的多個(gè)類之間沒(méi)有任何關(guān)系,不可以互相賦值
          //     即Pair<Number> p1;  Pair<Integer> p2;   p1=p2;  這種賦值是錯(cuò)誤的。
          //9、若某個(gè)范型類還有同名的非范型類,不要混合使用,堅(jiān)持使用范型類
          //     Pair<Manager> managerBuddies = new Pair<Manager>(ceo, cfo);
          //     Pair rawBuddies = managerBuddies;  這里編譯器不會(huì)報(bào)錯(cuò),但存在著嚴(yán)重的運(yùn)行時(shí)錯(cuò)誤隱患

          文章來(lái)源:http://wintys.blog.51cto.com/425414/89225
          posted on 2009-03-18 12:02 天堂露珠 閱讀(196) 評(píng)論(0)  編輯  收藏 所屬分類: Java
          主站蜘蛛池模板: 南宁市| 海原县| 濮阳县| 三都| 平乡县| 闽侯县| 五莲县| 河池市| 永和县| 东乌| 独山县| 金沙县| 石狮市| 运城市| 同德县| 玛多县| 霍邱县| 大厂| 昌乐县| 乡宁县| 龙门县| 张家口市| 弋阳县| 长岭县| 咸宁市| 云南省| 普洱| 兴隆县| 巴东县| 大厂| 东至县| 伊春市| 莱西市| 虞城县| 南岸区| 澄迈县| 福州市| 崇文区| 革吉县| 台山市| 东宁县|