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

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

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

    隨筆 - 117  文章 - 72  trackbacks - 0

    聲明:原創作品(標有[原]字樣)轉載時請注明出處,謝謝。

    常用鏈接

    常用設置
    常用軟件
    常用命令
     

    訂閱

    訂閱

    留言簿(7)

    隨筆分類(130)

    隨筆檔案(123)

    搜索

    •  

    積分與排名

    • 積分 - 155671
    • 排名 - 391

    最新評論

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

        從jdk1.5開始,Java中開始支持范型了。范型是一個很有用的編程工具,給我們帶來了極大的靈活性。在看了《java核心編程》之后,我小有收獲,寫出來與大家分享。
        所謂范型,我的感覺就是,不用考慮對象的具體類型,就可以對對象進行一定的操作,對任何對象都能進行同樣的操作。這就是靈活性之所在。但是,正是因為沒有 考慮對象的具體類型,因此一般情況下不可以使用對象自帶的接口函數,因為不同的對象所攜帶的接口函數不一樣,你使用了對象A的接口函數,萬一別人將一個對 象B傳給范型,那么程序就會出現錯誤,這就是范型的局限性。所以說,范型的最佳用途,就是用于實現容器類,實現一個通用的容器。該容器可以存儲對象,也可 以取出對象,而不用考慮對象的具體類型。因此,在學習范型的時候,一定要了解這一點,你不能指望范型是萬能的,要充分考慮到范型的局限性。下面我們來探討 一下范型的原理以及高級應用。首先給出一個范型類:
    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類是一個容器類(我會多次強調,范型天生就是為了容器類的方便實現),容納了2個數據,但這2個數據類型是不確定的,用范型T來表示。關于范型類如何使用,那是最基本的內容,在此就不討論了。
            下面我們來討論一下Java中范型類的實現原理。在java中,范型是在編譯器中實現的,而不是在虛擬機中實現的,虛擬機對范型一無所知。因此,編譯器一 定要把范型類修改為普通類,才能夠在虛擬機中執行。在java中,這種技術稱之為“擦除”,也就是用Object類型替換范型。上述代碼經過擦除后就變成 如下形式:
    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; 
    }
     

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

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

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

            下面我們再來考察一個更復雜的情況,如果我們的Pair類要保證第二個屬性一定要大于第一個屬性,該如何做?這就涉及到兩個屬性的比較,但是這2個屬性類 型未知,可以比較嗎?我們前面也講過,一般情況下不要涉及類型的具體信息。但是現在要比較2個屬性,不得不涉及類型的具體信息了。Java還是考慮到了這 一點,那就是,范型類可以繼承自某一個父類,或者實現某個接口,或者同時繼承父類并且實現接口。這樣的話,就可以對類型調用父類或接口中定義的方法了。代 碼如下:
    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被我們添加了一個約束條件,那就是他必須實現Comparable接口,這樣的話,我們就可以對范型T使用接口中定義的方法了,也 就可以實現2個元素大小的比較。有人可能要問了,實現一個接口不是用implements嗎?上面怎么用extends呢??為了簡化范型的設計,無論是 繼承類還是實現接口,一律使用extends關鍵字。這是規定,沒辦法,記住就行了。若同時添加多個約束,各個約束之間用“&”分隔,比 如:public class Pair<T extends Comparable & Serializable>。那么編譯器是如何處理這種情況呢?前面講過,范型類最終都會被轉化為原生類。在前面沒有添加約束的時候,編譯器將范型 通通替換為Object;而增加了約束之后,通通用第一個約束來替換范型(上面的代碼就會用
    Comparable來替換所有范型),當需要用到其他約束中定義的方法的時候,通過插入強制轉化代碼來實現。在此就不給出具體的例子了。
            下面我們來看看最后一個知識點,定義一個函數,該函數接受一個范型類作為參數。首先讓我們來看一個最簡單的情況,參數是一個實例化的范型類:
        public static void test(ArrayList<Number> l) {
            l.add(
    new Integer(2));
        }

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

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

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


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

            按照上述代碼的寫法,只要我們對形參添加了一定的約束條件,那么我們在傳遞實參的時候,對實參的嚴格約束就會降低一些。上述代碼都指定了一個類 Number,并用了extends關鍵字,因此,在傳遞實參的時候,凡是從Number繼承的類組成的鏈表,均可以傳遞進去。但上面代碼的注釋中也說的 很清楚,為了不出現運行時錯誤,編譯器會對你調用的方法做嚴格的限制:凡是參數為范型的方法,一律不需調用!!
    l.get(0)是合法的,因為參數是整型而不是范型;l.add(x)就不合法,因為add函數的參數是范型。但是定義一個范型方法還是有一定靈活性的,如果傳入的數據也是范型,編譯器還是認可的,因為范型對范型,類型安全是可以保證的。
            從上述代碼可以看出,定義一個范型方法要比Wildcard稍微靈活一些,可以往鏈表中添加T類型的對象,而Wildcard中是不允許往鏈表中添加任何類型的對象的。那么我們還要Wildcard干什么呢?
    Wildcard還是有他存在的意義的,那就是,Wildcard支持另外一個關鍵字super,而范型方法不支持super關鍵字。換句話說,如果你要實現這樣的功能:“傳入的參數應該是指定類的父類”,范型方法就無能為力了,只能依靠Wildcard來實現。代碼如下:
        public static void test5(ArrayList<? super Integer> l) {
            Integer n 
    = new Integer(45);
            l.add(n);  
    //與上面使用extends關鍵字相反,往鏈表里面插入指定類型的數據是被允許的。
            Object x = l.get(0); //從鏈表里取出一個數據仍然是被允許的,不過要賦值給Object對象。
            l.add(x);   //錯誤!!將剛剛取出的數據再次插入鏈表是不被允許的。
        }
            這種實現方式的特點我們前面已經說過了,就是對實參的限制更改為:必須是指定類型的父類。這里我們指定了Integer類,那么實參鏈表的元素類型,必須 是Number類及其父類。下面我們重點討論一下上述代碼的第四條語句,為什么將剛剛取出的數據再次插入鏈表不被允許??道理很簡單,剛剛取出的數據被保 存在一個Object類型的引用中,而鏈表的add方法只能接受指定類型Integer及其子類,類型不匹配當然不行。有些人可能立刻會說,我將他強制轉 化為Integer類(即
    l.add((Integer)x); , 編譯器不就不報錯了嗎?確實,經過強制轉化后,編譯器確實沒意見了。不過這種強制轉化有可能帶來運行時錯誤。因為你傳入的實參,其元素類型是 Integer的父類,比如是Number。那么,存儲在該鏈表中的第一個數據,很有可能是Double或其他類型的,這是合法的。那么你取出的第一個元 素x也會是Double類型。那么你把一個Double類型強制轉化為Integer類型,顯然是一個運行時錯誤。
            難道“把取出的元素再插入到鏈表中”這樣一個功能就實現不了嗎?當然可以,不過不能直接實現,要借助范型函數的幫忙,因為在范型函數中,剛剛取出的元素再存回去是不成問題的。定義這樣一個范型函數,我們稱之為幫助函數。代碼如下:
        //幫助函數
        public static <T>void helperTest5(ArrayList<T> l, int index) {
            T temp 
    = l.get(index);
            l.add(temp);
        }

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

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

    文章來源:http://wintys.blog.51cto.com/425414/89225
    posted on 2009-03-18 12:02 天堂露珠 閱讀(193) 評論(0)  編輯  收藏 所屬分類: Java
    主站蜘蛛池模板: 日韩免费高清播放器| 国产大片线上免费观看| 亚洲欧洲日产国产综合网| 国产亚洲综合视频| 国产偷国产偷亚洲清高动态图| 黄网站免费在线观看| 亚洲精品第一国产综合野| 久久精品国产亚洲一区二区三区| 久久久久久一品道精品免费看| 亚洲变态另类一区二区三区| 亚洲伊人久久精品影院| 无人影院手机版在线观看免费| 搜日本一区二区三区免费高清视频 | 色猫咪免费人成网站在线观看 | 杨幂最新免费特级毛片| 婷婷精品国产亚洲AV麻豆不片| 免费观看a级毛片| 爱丫爱丫影院在线观看免费| 亚洲精品国产suv一区88| 亚洲AV无码成人网站久久精品大| 在线免费观看一级毛片| 免费毛片a线观看| 免费的黄色的网站| 亚洲不卡在线观看| 亚洲AV无码码潮喷在线观看 | 亚洲精品中文字幕乱码三区| 18禁成年无码免费网站无遮挡| 七色永久性tv网站免费看| 在线91精品亚洲网站精品成人| 亚洲性69影院在线观看| 亚洲深深色噜噜狠狠爱网站| 国产男女猛烈无遮档免费视频网站 | 16女性下面扒开无遮挡免费| 成人免费视频一区二区| 国产亚洲sss在线播放| 亚洲国产女人aaa毛片在线| 亚洲国产精品自在拍在线播放| 巨胸喷奶水视频www网免费| 亚欧人成精品免费观看| 国产免费无码一区二区| eeuss影院ss奇兵免费com|