Changes between Initial Version and Version 1 of Generic


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

--

Legend:

Unmodified
Added
Removed
Modified
  • Generic

    v1 v1  
     1當您定義類別時,發現到好幾個類別的邏輯其實都相同,就只是當中所涉及的型態不一樣時,使用複製、貼上、取代的功能來撰寫程式只是讓您增加不必要的檔案管理困擾。
     2
     3由於Java中所有定義的類別,都以Object為最上層的父類別,所以在 J2SE 5.0 之前,Java程式設計人員可以使用Object來解決上面這樣的需求,為了讓定義出來的類別可以更加通用(Generic),傳入的值或傳回的物件都是以Object為主,當您要取出這些物件來使用時,必須記得將介面轉換為原來的類型,這樣才可以操作物件上的方法。
     4
     5然而使用Object來撰寫泛型類別(Generic Class)留下了一個問題,因為您必須要轉換介面,粗心的程式設計人員往往會忘了要作這個動作,或者是轉換介面時用錯了型態(像是該用Boolean卻用了Integer),要命的是,語法上是可以的,所以編譯器檢查不出錯誤,真正的錯誤要在執行時期才會發生,這時惱人的ClassCastException就會出來搞怪,在使用Object設計泛型程式時,程式人員要再細心一些、小心一些。
     6
     7在J2SE 5.0之後,提出了針對泛型(Generics)設計的解決方案,要定義一個簡單的泛型類別是簡單的,直接來看個例子:
     8
     9    * GenericFoo.java
     10{{{
     11#!java
     12public class GenericFoo<T> {
     13    private T foo;
     14 
     15    public void setFoo(T foo) {
     16        this.foo = foo;
     17    }
     18 
     19    public T getFoo() {
     20        return foo;
     21    }
     22}
     23}}}
     24
     25<T> 用來宣告一個型態持有者(Holder)T,之後您可以用 T 作為型態代表來宣告變數(參考)名稱,然後您可以像下面的程式來使用這個類別:
     26{{{
     27GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>();
     28GenericFoo<Integer> foo2 = new GenericFoo<Integer>();
     29 
     30foo1.setFoo(new Boolean(true));
     31Boolean b = foo1.getFoo();
     32 
     33foo2.setFoo(new Integer(10));
     34Integer i = foo2.getFoo();
     35}}}
     36
     37回過頭來看看下面的宣告:
     38{{{
     39GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>();
     40GenericFoo<Integer> foo2 = new GenericFoo<Integer>();
     41 }}}
     42
     43GenericFoo< Boolean>宣告的foo1與GenericFoo< Integer>宣告的foo2是相同的類型嗎?答案是否定的,基本上它們分屬於兩個不同類別的類型,即「相當於」下面兩個類型(只是個比喻):
     44{{{
     45public class GenericFooBoolean {
     46    private Boolean foo;
     47 
     48    public void setFoo(Boolean foo) {
     49        this.foo = foo;
     50    }
     51 
     52    public Boolean getFoo() {
     53        return foo;
     54    }
     55}
     56}}}
     57以及:
     58{{{
     59public class GenericFooInteger {
     60    private Integer foo;
     61 
     62    public void setFoo(Integer foo) {
     63        this.foo = foo;
     64    }
     65 
     66    public Integer getFoo() {
     67        return foo;
     68    }
     69}
     70}}}
     71
     72所以您不可以將 foo1 指定給 foo2,或是將 foo2 指定給 foo1,編譯器會回報錯誤。[[BR]]
     73[[BR]]
     74
     75 = 幾個定義泛型的例子 =
     76您可以在定義泛型類別時,宣告多個類型持有者,例如:
     77
     78    * GenericFoo.java
     79
     80{{{
     81public class GenericFoo<T1, T2> {
     82    private T1 foo1;
     83    private T2 foo2;
     84 
     85    public void setFoo1(T1 foo1) {
     86        this.foo1 = foo1;
     87    }
     88 
     89    public T1 getFoo1() {
     90        return foo1;
     91    }
     92 
     93    public void setFoo2(T2 foo2) {
     94        this.foo2 = foo2;
     95    }
     96 
     97    public T2 getFoo2() {
     98        return foo2;
     99    }
     100}
     101}}}
     102您可以如下使用GenericFoo類別,分別以Integer與Boolean取代T1與T2:[[BR]]
     103
     104
     105GenericFoo<Integer, Boolean> foo = new GenericFoo<Integer, Boolean>();[[BR]]
     106
     107
     108如果是陣列的話,可以像這樣:
     109
     110    * GenericFoo.java
     111
     112{{{
     113public class GenericFoo<T> {
     114    private T[] fooArray;
     115
     116    public void setFooArray(T[] fooArray) {
     117        this.fooArray = fooArray;
     118    }
     119
     120    public T[] getFooArray() {
     121        return fooArray;
     122    }
     123}
     124}}}
     125
     126您可以像下面的方式來使用它:
     127{{{
     128String[] strs = {"caterpillar", "momor", "bush"};
     129 
     130GenericFoo<String> foo = new GenericFoo<String>();
     131foo.setFooArray(strs);
     132strs = foo.getFooArray();
     133}}}
     134來改寫一下 Object 類別 中的 SimpleCollection:
     135
     136    * SimpleCollection.java
     137
     138{{{
     139public class SimpleCollection<T> {
     140    private T[] objArr;
     141    private int index = 0;
     142 
     143    public SimpleCollection() {
     144        objArr = (T[]) new Object[10]; // 預設10個物件空間
     145    }
     146 
     147    public SimpleCollection(int capacity) {
     148        objArr = (T[]) new Object[capacity];
     149    }
     150 
     151    public void add(T t) {
     152        objArr[index] = t;
     153        index++;
     154    }
     155 
     156    public int getLength() {
     157        return index;
     158    }
     159 
     160    public T get(int i) {
     161        return (T) objArr[i];
     162    }
     163}
     164}}}
     165
     166現在您可以直接使用它來當作特定類型物件的容器,例如:
     167
     168    * Test.java
     169{{{
     170public class Test {
     171    public static void main(String[] args) {
     172        SimpleCollection<Integer> c =
     173                new SimpleCollection<Integer>();
     174 
     175        for(int i = 0; i < 10; i++) {
     176            c.add(new Integer(i));
     177        }
     178
     179        for(int i = 0; i < 10; i++) {
     180            Integer k = c.get(i);
     181        }
     182    }
     183}
     184}}}
     185
     186另一個SimpleCollection的寫法也可以如下,作用是一樣的:
     187
     188    * SimpleCollection.java
     189{{{
     190public class SimpleCollection<T> {
     191    private Object[] objArr;
     192    private int index = 0;
     193 
     194    public SimpleCollection() {
     195        objArr = new Object[10]; // 預設10個物件空間
     196    }
     197 
     198    public SimpleCollection(int capacity) {
     199        objArr = new Object[capacity];
     200    }
     201 
     202    public void add(T t) {
     203        objArr[index] = t;
     204        index++;
     205    }
     206 
     207    public int getLength() {
     208        return index;
     209    }
     210 
     211    public T get(int i) {
     212        return (T) objArr[i];
     213    }
     214}
     215}}}
     216
     217如果您已經定義了一個泛型類別,想要用這個類別來於另一個泛型類別中宣告成員的話要如何作?舉個實例,假設您已經定義了下面的類別:
     218
     219    * GenericFoo.java
     220{{{
     221public class GenericFoo<T> {
     222    private T foo;
     223 
     224    public void setFoo(T foo) {
     225        this.foo = foo;
     226    }
     227 
     228    public T getFoo() {
     229        return foo;
     230    }
     231}
     232}}}
     233
     234您想要寫一個包裝類別(Wrapper),這個類別必須也具有GenericFoo的泛型功能,您可以這麼寫:
     235
     236    * WrapperFoo.java
     237{{{
     238public class WrapperFoo<T> {
     239    private GenericFoo<T> foo;
     240   
     241    public void setFoo(GenericFoo<T> foo) {
     242        this.foo = foo;
     243    }
     244 
     245    public GenericFoo<T> getFoo() {
     246        return foo;
     247    }
     248}
     249}}}
     250
     251
     252這麼一來,您就可以保留型態持有者 T 的功能,一個使用的例子如下:
     253{{{
     254GenericFoo<Integer> foo = new GenericFoo<Integer>();
     255foo.setFoo(new Integer(10));
     256 
     257WrapperFoo<Integer> wrapper = new WrapperFoo<Integer>();
     258wrapper.setFoo(foo);
     259}}}
     260
     261 = 限制泛型可用類型 =
     262在定義泛型類別時,預設您可以使用任何的型態來實例化泛型類別中的型態持有者,但假設您想要限制使用泛型類別時,只能用某個特定型態或其子類別才能實例化型態持有者的話呢?[[BR]]
     263
     264您可以在定義型態持有者時,一併使用"extends"指定這個型態持有者必須是擴充某個類型,舉個實例來說:
     265
     266    * ListGenericFoo.java
     267
     268{{{
     269import java.util.List;
     270
     271public class ListGenericFoo<T extends List> {
     272    private T[] fooArray;
     273
     274    public void setFooArray(T[] fooArray) {
     275        this.fooArray = fooArray;
     276    }
     277
     278    public T[] getFooArray() {
     279        return fooArray;
     280    }
     281}
     282}}}
     283
     284ListGenericFoo在宣告類型持有者時,一併指定這個持有者必須擴充自List介面(interface),在限定持有者時,無論是要限定的對象是介面或類別,都是使用"extends"關鍵字。[[BR]]
     285[[BR]]
     286
     287
     288您使用"extends"限定型態持有者必須是實作List的類別或其子類別,例如LinkedList與ArrayList,下面的程式是合法的:[[BR]]
     289{{{
     290ListGenericFoo<LinkedList> foo1 =
     291                  new ListGenericFoo<LinkedList>();
     292ListGenericFoo<ArrayList> foo2 =
     293                  new ListGenericFoo<ArrayList>();
     294}}}
     295
     296但是如果不是List的類別或是其子類別,就會發生編譯錯誤,例如下面的程式通不過編譯:[[BR]]
     297[[BR]]
     298
     299'''ListGenericFoo<HashMap> foo3 = new ListGenericFoo<HashMap>();'''[[BR]]
     300[[BR]]
     301
     302編譯器會回報以下錯誤訊息:[[BR]]
     303type parameter java.util.HashMap is not within its bound[[BR]]
     304ListGenericFoo<HashMap> foo3 = new ListGenericFoo<HashMap>();[[BR]][[BR]]
     305
     306HashMap並沒有實作List介面,所以無法用來實例化型態持有者,事實上,當您沒有使用extends關鍵字限定型態持有者時,預設則是Object下的所有子類別都可以實例化型態持有者,即只寫<T>時就相當於<T extends Object>。[[BR]]
     307
     308 = 型態通配字元 =
     309
     310假設您撰寫了一個泛型類別:
     311
     312    * GenericFoo.java
     313{{{
     314public class GenericFoo<T> {
     315    private T foo;
     316 
     317    public void setFoo(T foo) {
     318        this.foo = foo;
     319    }
     320 
     321    public T getFoo() {
     322        return foo;
     323    }
     324}
     325}}}
     326
     327分別使用下面的程式宣告了foo1與foo2兩個參考名稱:[[BR]]
     328[[BR]]
     329
     330'''GenericFoo<Integer> foo1 = null;[[BR]]
     331
     332GenericFoo<Boolean> foo2 = null;'''[[BR]]
     333
     334 
     335
     336那麼 foo1 就只接受GenericFoo<Integer>的實例,而foo2只接受GenericFoo<Boolean>的實例。[[BR]]
     337[[BR]]
     338
     339現在您有這麼一個需求,您希望有一個參考名稱foo可以接受所有下面的實例(List、Map或List介面以及其實介面的相關類別,在J2SE 5.0中已經針對泛型功能作了改寫,在這邊仍請將之當作介面就好,這是為了簡化說明的考量):[[BR]][[BR]]
     340
     341'''foo = new GenericFoo<ArrayList>();
     342foo = new GenericFoo<LinkedList>();'''[[BR]][[BR]]
     343
     344簡單的說,實例化型態持有者時,它必須是實作List的類別或其子類別,要宣告這麼一個參考名稱,您可以使用 '?' 通配字元,並使用"extends"關鍵字限定型態持有者的型態,例如[[BR]]
     345[[BR]]
     346
     347'''GenericFoo<? extends List> foo = null;
     348foo = new GenericFoo<ArrayList>();
     349.....
     350foo = new GenericFoo<LinkedList>();
     351....'''[[BR]]
     352[[BR]]
     353
     354 
     355
     356如果指定了不是實作List的類別或其子類別,則編譯器會回報錯誤,例如:[[BR]]
     357[[BR]]
     358
     359'''GenericFoo<? extends List> foo = new GenericFoo<HashMap>();'''[[BR]]
     360
     361上面這段程式編譯器會回報以下的錯誤:
     362{{{
     363incompatible types
     364found : GenericFoo<java.util.HashMap>
     365required: GenericFoo<? extends java.util.List>
     366GenericFoo<? extends List> foo = new GenericFoo<HashMap>();
     367}}}
     368
     369這樣的限定是很有用的,例如如果您想要自訂一個showFoo()方法,方法的內容實作是針對List而制定的,例如:[[BR]]
     370[[BR]]
     371
     372public void showFoo(GenericFoo foo) {[[BR]]
     373     // 針對List而制定的內容[[BR]]
     374}[[BR]]
     375[[BR]]
     376
     377您當然不希望任何的型態都可以傳入showFoo()方法中,您可以使用以下的方式來限定,例如:
     378'''public void showFoo(GenericFoo<? extends List> foo) {[[BR]]
     379
     380}'''[[BR]]
     381[[BR]]
     382
     383 
     384這麼一來,如果有粗心的程式設計人員傳入了您不想要的型態,例如GenericFoo<Boolean>型態的實例,則編譯器都會告訴它這是不可行的,在宣告名稱時如果指定了<?>而不使用"extends",則預設是允許Object及其下的子類,也就是所有的Java物件了,那為什麼不直接使用GenericFoo宣告就好了,何必要用GenericFoo<?>來宣告?使用通配字元有點要注意的是,透過使用通配字元宣告的名稱所參考的物件,您沒辦法再對它加入新的資訊,您只能取得它的資訊或是移除它的資訊,例如:
     385{{{
     386GenericFoo<String> foo = new GenericFoo<String>();
     387foo.setFoo("caterpillar");
     388GenericFoo<?> immutableFoo = foo;
     389
     390// 可以取得資訊
     391System.out.println(immutableFoo.getFoo());
     392
     393// 可透過immutableFoo來移去foo所參考實例內的資訊
     394immutableFoo.setFoo(null);
     395
     396// 不可透過immutableFoo來設定新的資訊給foo所參考的實例
     397// 所以下面這行無法通過編譯
     398//  immutableFoo.setFoo("良葛格");
     399}}}
     400所以使用<?>或是<? extends SomeClass>的宣告方式,意味著您只能透過該名稱來取得所參考實例的資訊,或者是移除某些資訊,但不能增加它的資訊,因為只知道當中放置的是SomeClass的子類,但不確定是什麼類的實例,編譯器不讓您加入物件,理由是,如果可以加入物件的話,那麼您就得記得取回的物件實例是什麼形態,然後轉換為原來的型態方可進行操作,這就失去了使用泛型的意義。[[BR]]
     401[[BR]]
     402
     403事實上,GenericFoo<?> immutableFoo相當於GenericFoo immutableFoo。[[BR]]
     404[[BR]]
     405
     406
     407
     408除了可以向下限制,您也可以向上限制,只要使用"super"關鍵字,例如:[[BR]]
     409[[BR]]
     410
     411'''GenericFoo<? super StringBuilder> foo;'''[[BR]]
     412
     413 
     414如此,foo就只接受 StringBuilder 及其上層的父類型態之物件。[[BR]]
     415
     416 = 擴充泛型類別、實作泛型介面 =
     417您可以擴充一個泛型類別,保留其型態持有者,並新增自己的型態持有者,例如先寫一個父類別:
     418
     419    * GenericFoo.java
     420{{{
     421public class GenericFoo<T1, T2> {
     422    private T1 foo1;
     423    private T2 foo2;
     424 
     425    public void setFoo1(T1 foo1) {
     426        this.foo1 = foo1;
     427    }
     428 
     429    public T1 getFoo1() {
     430        return foo1;
     431    }
     432 
     433    public void setFoo2(T2 foo2) {
     434        this.foo2 = foo2;
     435    }
     436 
     437    public T2 getFoo2() {
     438        return foo2;
     439    }
     440}
     441}}}
     442
     443
     444再來寫一個子類別擴充上面的父類別:
     445{{{
     446    * SubGenericFoo.java
     447
     448public class SubGenericFoo<T1, T2, T3>
     449                               extends GenericFoo<T1, T2> {
     450    private T3 foo3;
     451 
     452    public void setFoo3(T3 foo3) {
     453        this.foo3 = foo3;
     454    }
     455 
     456    public T3 getFoo3() {
     457        return foo3;
     458    }
     459}
     460}}}
     461如果決定要保留型態持有者,則父類別上宣告的型態持有者數目必須齊全,也就是說上式中,T1與T2都要出現,如果不保留型態持有者,則繼承下來的T1與 T2自動變為Object,建議當然是父類別的型態持有者都保留。
     462
     463介面實作也是類似,例如先定義一個介面:
     464
     465    * IFoo.java
     466{{{
     467public interface IFoo<T1, T2> {
     468    public void setFoo1(T1 foo1);
     469    public void setFoo2(T2 foo2);
     470    public T1 getFoo1();
     471    public T2 getFoo2();
     472}
     473}}}
     474
     475實作時如下,保留所有的型態持有者:
     476
     477    * GenericFoo.java
     478{{{
     479public class GenericFoo<T1, T2> implements IFoo<T1, T2> {
     480    private T1 foo1;
     481    private T2 foo2;
     482 
     483    public void setFoo1(T1 foo1) {
     484        this.foo1 = foo1;
     485    }
     486 
     487    public T1 getFoo1() {
     488        return foo1;
     489    }
     490 
     491    public void setFoo2(T2 foo2) {
     492        this.foo2 = foo2;
     493    }
     494 
     495    public T2 getFoo2() {
     496        return foo2;
     497    }
     498}
     499}}}