From Gossip@caterpillar

Java Gossip: 定義泛型類別

當您定義類別時,發現到好幾個類別的邏輯其實都相同,就只是當中所涉及的型態不一樣時,使用複製、貼上、取代的功能來撰寫程式只是讓您增加不必要的檔案管理困擾。

由於Java中所有定義的類別,都以Object為最上層的父類別,所以在 J2SE 5.0 之前,Java程式設計人員可以使用Object來解決上面這樣的需求,為了讓定義出來的類別可以更加通用(Generic),傳入的值或傳回的物件都是以Object為主,當您要取出這些物件來使用時,必須記得將介面轉換為原來的類型,這樣才可以操作物件上的方法。

然而使用Object來撰寫泛型類別(Generic Class)留下了一個問題,因為您必須要轉換介面,粗心的程式設計人員往往會忘了要作這個動作,或者是轉換介面時用錯了型態(像是該用Boolean卻 用了Integer),要命的是,語法上是可以的,所以編譯器檢查不出錯誤,真正的錯誤要在執行時期才會發生,這時惱人的ClassCastException就會出來搞怪,在使用Object設計泛型程式時,程式人員要再細心一些、小心一些。

在J2SE 5.0之後,提出了針對泛型(Generics)設計的解決方案,要定義一個簡單的泛型類別是簡單的,直接來看個例子:

  • GenericFoo.java
public class GenericFoo<T> {
private T foo;

public void setFoo(T foo) {
this.foo = foo;
}

public T getFoo() {
return foo;
}
}

<T> 用來宣告一個型態持有者(Holder)T,之後您可以用 T 作為型態代表來宣告變數(參考)名稱,然後您可以像下面的程式來使用這個類別:
GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>();
GenericFoo<Integer> foo2 = new GenericFoo<Integer>();
 
foo1.setFoo(new Boolean(true));
Boolean b = foo1.getFoo();
 
foo2.setFoo(new Integer(10));
Integer i = foo2.getFoo();
 

不同的地方在於,在宣告與配置物件時,您可以一併指定泛型類別中真正的型態,這將用來取代定義時所使用的 T,而這次您可以看到,介面轉換不再需要了,所定義出來的泛型類別在使用時多了一層安全性,至少可以省去惱人的ClassCastException 發生,編譯器可以幫您作第一層防線,例如下面的程式會被檢查出錯誤:
GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>();
 
foo1.setFoo(new Boolean(true));
Integer b = foo1.getFoo();
 

foo1.getFoo()傳回的是Boolean,您要將它指定給Integer,這顯然語法上不合,編譯器這時可以派上用場:
Test.java:7: incompatible types
found : java.lang.Boolean
required: java.lang.Integer
Integer b = foo1.getFoo();

如果使用泛型類別,但宣告時不一併指定型態呢?那麼預設會使用Object,不過您就要自己轉換物件的介面型態了,但編譯器會提出警訊,告訴您這可能是不安全的操作:
Note: Test.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

回過頭來看看下面的宣告:
GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>();
GenericFoo<Integer> foo2 = new GenericFoo<Integer>();
 

GenericFoo< Boolean>宣告的foo1與GenericFoo< Integer>宣告的foo2是相同的類型嗎?答案是否定的,基本上它們分屬於兩個不同類別的類型,即「相當於」下面兩個類型(只是個比喻):
public class GenericFooBoolean {
    private Boolean foo;
 
    public void setFoo(Boolean foo) {
        this.foo = foo;
    }
 
    public Boolean getFoo() {
        return foo;
    }
}
 
以及:
public class GenericFooInteger {
    private Integer foo;
 
    public void setFoo(Integer foo) {
        this.foo = foo;
    }
 
    public Integer getFoo() {
        return foo;
    }
}
 

所以您不可以將 foo1 指定給 foo2,或是將 foo2 指定給 foo1,編譯器會回報以下錯誤:
incompatible types
found : GenericFoo<java.lang.Integer>
required: GenericFoo<java.lang.Boolean>
foo1 = foo2;