Changes between Version 1 and Version 2 of Generic


Ignore:
Timestamp:
Jul 10, 2008, 4:28:16 PM (16 years ago)
Author:
waue
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Generic

    v1 v2  
    33由於Java中所有定義的類別,都以Object為最上層的父類別,所以在 J2SE 5.0 之前,Java程式設計人員可以使用Object來解決上面這樣的需求,為了讓定義出來的類別可以更加通用(Generic),傳入的值或傳回的物件都是以Object為主,當您要取出這些物件來使用時,必須記得將介面轉換為原來的類型,這樣才可以操作物件上的方法。
    44
    5 然而使用Object來撰寫泛型類別(Generic Class)留下了一個問題,因為您必須要轉換介面,粗心的程式設計人員往往會忘了要作這個動作,或者是轉換介面時用錯了型態(像是該用Boolean卻用了Integer),要命的是,語法上是可以的,所以編譯器檢查不出錯誤,真正的錯誤要在執行時期才會發生,這時惱人的ClassCastException就會出來搞怪,在使用Object設計泛型程式時,程式人員要再細心一些、小心一些。
     5然而使用Object來撰寫泛型類別(Generic Class)留下了一個問題,因為您必須要轉換介面,粗心的程式設計人員往往會忘了要作這個動作,或者是轉換介面時用錯了型態(像是該用Boolean卻用了Integer),要命的是,語法上是可以的,所以編譯器檢查不出錯誤,真正的錯誤要在執行時期才會發生,這時惱人的!ClassCastException就會出來搞怪,在使用Object設計泛型程式時,程式人員要再細心一些、小心一些。
    66
    77在J2SE 5.0之後,提出了針對泛型(Generics)設計的解決方案,要定義一個簡單的泛型類別是簡單的,直接來看個例子:
    88
    9     * GenericFoo.java
     9    * !GenericFoo.java
    1010{{{
    1111#!java
     
    2525<T> 用來宣告一個型態持有者(Holder)T,之後您可以用 T 作為型態代表來宣告變數(參考)名稱,然後您可以像下面的程式來使用這個類別:
    2626{{{
     27#!java
    2728GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>();
    2829GenericFoo<Integer> foo2 = new GenericFoo<Integer>();
     
    3738回過頭來看看下面的宣告:
    3839{{{
     40#!java
    3941GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>();
    4042GenericFoo<Integer> foo2 = new GenericFoo<Integer>();
    4143 }}}
    4244
    43 GenericFoo< Boolean>宣告的foo1與GenericFoo< Integer>宣告的foo2是相同的類型嗎?答案是否定的,基本上它們分屬於兩個不同類別的類型,即「相當於」下面兩個類型(只是個比喻):
    44 {{{
     45!GenericFoo< Boolean>宣告的foo1與!GenericFoo< Integer>宣告的foo2是相同的類型嗎?答案是否定的,基本上它們分屬於兩個不同類別的類型,即「相當於」下面兩個類型(只是個比喻):
     46{{{
     47#!java
    4548public class GenericFooBoolean {
    4649    private Boolean foo;
     
    5760以及:
    5861{{{
     62#!java
    5963public class GenericFooInteger {
    6064    private Integer foo;
     
    7680您可以在定義泛型類別時,宣告多個類型持有者,例如:
    7781
    78     * GenericFoo.java
    79 
    80 {{{
     82    * !GenericFoo.java
     83
     84{{{
     85#!java
    8186public class GenericFoo<T1, T2> {
    8287    private T1 foo1;
     
    100105}
    101106}}}
    102 您可以如下使用GenericFoo類別,分別以Integer與Boolean取代T1與T2:[[BR]]
    103 
    104 
    105 GenericFoo<Integer, Boolean> foo = new GenericFoo<Integer, Boolean>();[[BR]]
     107您可以如下使用!GenericFoo類別,分別以Integer與Boolean取代T1與T2:[[BR]]
     108
     109
     110!GenericFoo<Integer, Boolean> foo = new !GenericFoo<Integer, Boolean>();[[BR]]
    106111
    107112
    108113如果是陣列的話,可以像這樣:
    109114
    110     * GenericFoo.java
    111 
    112 {{{
     115    * !GenericFoo.java
     116
     117{{{
     118#!java
    113119public class GenericFoo<T> {
    114120    private T[] fooArray;
     
    126132您可以像下面的方式來使用它:
    127133{{{
     134#!java
    128135String[] strs = {"caterpillar", "momor", "bush"};
    129136 
     
    132139strs = foo.getFooArray();
    133140}}}
    134 來改寫一下 Object 類別 中的 SimpleCollection:
    135 
    136     * SimpleCollection.java
    137 
    138 {{{
     141來改寫一下 Object 類別 中的 !SimpleCollection:
     142
     143    * !SimpleCollection.java
     144
     145{{{
     146#!java
    139147public class SimpleCollection<T> {
    140148    private T[] objArr;
     
    168176    * Test.java
    169177{{{
     178#!java
    170179public class Test {
    171180    public static void main(String[] args) {
     
    184193}}}
    185194
    186 另一個SimpleCollection的寫法也可以如下,作用是一樣的:
    187 
    188     * SimpleCollection.java
    189 {{{
     195另一個!SimpleCollection的寫法也可以如下,作用是一樣的:
     196
     197    * !SimpleCollection.java
     198{{{
     199#!java
    190200public class SimpleCollection<T> {
    191201    private Object[] objArr;
     
    219229    * GenericFoo.java
    220230{{{
     231#!java
    221232public class GenericFoo<T> {
    222233    private T foo;
     
    232243}}}
    233244
    234 您想要寫一個包裝類別(Wrapper),這個類別必須也具有GenericFoo的泛型功能,您可以這麼寫:
    235 
    236     * WrapperFoo.java
    237 {{{
     245您想要寫一個包裝類別(Wrapper),這個類別必須也具有!GenericFoo的泛型功能,您可以這麼寫:
     246
     247    * !WrapperFoo.java
     248{{{
     249#!java
    238250public class WrapperFoo<T> {
    239251    private GenericFoo<T> foo;
     
    252264這麼一來,您就可以保留型態持有者 T 的功能,一個使用的例子如下:
    253265{{{
     266#!java
    254267GenericFoo<Integer> foo = new GenericFoo<Integer>();
    255268foo.setFoo(new Integer(10));
     
    264277您可以在定義型態持有者時,一併使用"extends"指定這個型態持有者必須是擴充某個類型,舉個實例來說:
    265278
    266     * ListGenericFoo.java
    267 
    268 {{{
     279    * !ListGenericFoo.java
     280
     281{{{
     282#!java
    269283import java.util.List;
    270284
     
    282296}}}
    283297
    284 ListGenericFoo在宣告類型持有者時,一併指定這個持有者必須擴充自List介面(interface),在限定持有者時,無論是要限定的對象是介面或類別,都是使用"extends"關鍵字。[[BR]]
    285 [[BR]]
    286 
    287 
    288 您使用"extends"限定型態持有者必須是實作List的類別或其子類別,例如LinkedList與ArrayList,下面的程式是合法的:[[BR]]
    289 {{{
     298!ListGenericFoo在宣告類型持有者時,一併指定這個持有者必須擴充自List介面(interface),在限定持有者時,無論是要限定的對象是介面或類別,都是使用"extends"關鍵字。[[BR]]
     299[[BR]]
     300
     301
     302您使用"extends"限定型態持有者必須是實作List的類別或其子類別,例如!LinkedList與!ArrayList,下面的程式是合法的:[[BR]]
     303{{{
     304#!java
    290305ListGenericFoo<LinkedList> foo1 =
    291306                  new ListGenericFoo<LinkedList>();
     
    297312[[BR]]
    298313
    299 '''ListGenericFoo<HashMap> foo3 = new ListGenericFoo<HashMap>();'''[[BR]]
     314'''!ListGenericFoo<!!HashMap> foo3 = new !ListGenericFoo<!!HashMap>();'''[[BR]]
    300315[[BR]]
    301316
    302317編譯器會回報以下錯誤訊息:[[BR]]
    303 type parameter java.util.HashMap is not within its bound[[BR]]
    304 ListGenericFoo<HashMap> foo3 = new ListGenericFoo<HashMap>();[[BR]][[BR]]
    305 
    306 HashMap並沒有實作List介面,所以無法用來實例化型態持有者,事實上,當您沒有使用extends關鍵字限定型態持有者時,預設則是Object下的所有子類別都可以實例化型態持有者,即只寫<T>時就相當於<T extends Object>。[[BR]]
     318type parameter java.util.!HashMap is not within its bound[[BR]]
     319!ListGenericFoo<!HashMap> foo3 = new !ListGenericFoo<!HashMap>();[[BR]][[BR]]
     320
     321!HashMap並沒有實作List介面,所以無法用來實例化型態持有者,事實上,當您沒有使用extends關鍵字限定型態持有者時,預設則是Object下的所有子類別都可以實例化型態持有者,即只寫<T>時就相當於<T extends Object>。[[BR]]
    307322
    308323 = 型態通配字元 =
     
    312327    * GenericFoo.java
    313328{{{
     329#!java
    314330public class GenericFoo<T> {
    315331    private T foo;
     
    339355現在您有這麼一個需求,您希望有一個參考名稱foo可以接受所有下面的實例(List、Map或List介面以及其實介面的相關類別,在J2SE 5.0中已經針對泛型功能作了改寫,在這邊仍請將之當作介面就好,這是為了簡化說明的考量):[[BR]][[BR]]
    340356
    341 '''foo = new GenericFoo<ArrayList>();
    342 foo = new GenericFoo<LinkedList>();'''[[BR]][[BR]]
     357'''foo = new GenericFoo<!ArrayList>();
     358foo = new GenericFoo<!LinkedList>();'''[[BR]][[BR]]
    343359
    344360簡單的說,實例化型態持有者時,它必須是實作List的類別或其子類別,要宣告這麼一個參考名稱,您可以使用 '?' 通配字元,並使用"extends"關鍵字限定型態持有者的型態,例如[[BR]]
     
    346362
    347363'''GenericFoo<? extends List> foo = null;
    348 foo = new GenericFoo<ArrayList>();
     364foo = new GenericFoo<!ArrayList>();
    349365.....
    350 foo = new GenericFoo<LinkedList>();
     366foo = new GenericFoo<!LinkedList>();
    351367....'''[[BR]]
    352368[[BR]]
     
    357373[[BR]]
    358374
    359 '''GenericFoo<? extends List> foo = new GenericFoo<HashMap>();'''[[BR]]
     375'''GenericFoo<? extends List> foo = new GenericFoo<!HashMap>();'''[[BR]]
    360376
    361377上面這段程式編譯器會回報以下的錯誤:
    362378{{{
     379#!java
    363380incompatible types
    364381found : GenericFoo<java.util.HashMap>
     
    384401這麼一來,如果有粗心的程式設計人員傳入了您不想要的型態,例如GenericFoo<Boolean>型態的實例,則編譯器都會告訴它這是不可行的,在宣告名稱時如果指定了<?>而不使用"extends",則預設是允許Object及其下的子類,也就是所有的Java物件了,那為什麼不直接使用GenericFoo宣告就好了,何必要用GenericFoo<?>來宣告?使用通配字元有點要注意的是,透過使用通配字元宣告的名稱所參考的物件,您沒辦法再對它加入新的資訊,您只能取得它的資訊或是移除它的資訊,例如:
    385402{{{
     403#!java
    386404GenericFoo<String> foo = new GenericFoo<String>();
    387405foo.setFoo("caterpillar");
     
    398416//  immutableFoo.setFoo("良葛格");
    399417}}}
    400 所以使用<?>或是<? extends SomeClass>的宣告方式,意味著您只能透過該名稱來取得所參考實例的資訊,或者是移除某些資訊,但不能增加它的資訊,因為只知道當中放置的是SomeClass的子類,但不確定是什麼類的實例,編譯器不讓您加入物件,理由是,如果可以加入物件的話,那麼您就得記得取回的物件實例是什麼形態,然後轉換為原來的型態方可進行操作,這就失去了使用泛型的意義。[[BR]]
     418所以使用<?>或是<? extends !SomeClass>的宣告方式,意味著您只能透過該名稱來取得所參考實例的資訊,或者是移除某些資訊,但不能增加它的資訊,因為只知道當中放置的是!SomeClass的子類,但不確定是什麼類的實例,編譯器不讓您加入物件,理由是,如果可以加入物件的話,那麼您就得記得取回的物件實例是什麼形態,然後轉換為原來的型態方可進行操作,這就失去了使用泛型的意義。[[BR]]
    401419[[BR]]
    402420
     
    409427[[BR]]
    410428
    411 '''GenericFoo<? super StringBuilder> foo;'''[[BR]]
    412 
    413  
    414 如此,foo就只接受 StringBuilder 及其上層的父類型態之物件。[[BR]]
     429'''GenericFoo<? super !StringBuilder> foo;'''[[BR]]
     430
     431 
     432如此,foo就只接受 !StringBuilder 及其上層的父類型態之物件。[[BR]]
    415433
    416434 = 擴充泛型類別、實作泛型介面 =
     
    419437    * GenericFoo.java
    420438{{{
     439#!java
    421440public class GenericFoo<T1, T2> {
    422441    private T1 foo1;
     
    444463再來寫一個子類別擴充上面的父類別:
    445464{{{
     465#!java
    446466    * SubGenericFoo.java
    447467
     
    465485    * IFoo.java
    466486{{{
     487#!java
    467488public interface IFoo<T1, T2> {
    468489    public void setFoo1(T1 foo1);
     
    477498    * GenericFoo.java
    478499{{{
     500#!java
    479501public class GenericFoo<T1, T2> implements IFoo<T1, T2> {
    480502    private T1 foo1;