2007年7月27日 星期五

Java 學習筆記 (6) - Generics

若要定義多個相似的 class,僅有在資料(變數、參數、回傳值)的型態上有所不同外,就是使用 Generic 一個相當合適的時機。

Generic(泛型) 的基本用法

這個功能是在 J2SE 5.0 中所提供的,目的是要協助程式開發者建立安全的 Generic Class;當然在沒有 Generic Class 之前,可透過 Object Class 並搭配 polymorphism 的功能達成類似的功能。

以下直接用程式範例來說明,可以比較兩者使用方式上的不同:

Object Class + Polymorphism 機制

ObjectFoo.java
//使用 polymorphism 的方式定義各型態可用的 class
public class ObjectFoo {
private Object foo;

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

public Object getFoo() {
return foo;
}
}
ObjectFooDemo.java
public class ObjectFooDemo {
public static void main(String[] args) {
ObjectFoo foo1 = new ObjectFoo();
ObjectFoo foo2 = new ObjectFoo();

foo1.setFoo(new Boolean(true));
Boolean b = (Boolean)foo1.getFoo(); //此處傳回 Object,因此必須轉換型態

foo2.setFoo(new Integer(10));
Integer i = (Integer)foo2.getFoo(); //此處傳回 Object,因此必須轉換型態
}
}

Generics (泛型)

【注意】在"型態"的部分,都是以「<T>」來代替。

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

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

public T getFoo() {
return foo;
}
}
GenericFooDemo.java
public class GenericFooDemo {
public static void main(String[] args) {
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(); //不需要再轉換型態
}
}

當然還有其他的用法,例如同時使用多種不同型態時:
public class GenericFoo2<T1, T2> {
private T1 foo1;
private T2 foo2;

public void setFoo1(T1 foo1) {
this.foo1 = foo1;
}

public T1 getFoo1() {
return foo1;
}

public void setFoo2(T2 foo2) {
this.foo2 = foo2;
}

public T2 getFoo2() {
return foo2;
}
}
或是與陣列的搭配使用:
public class GenericFoo3<T> {
private T[] fooArray;

public void setFooArray(T[] fooArray) {
this.fooArray = fooArray;
}

public T[] getFooArray() {
return fooArray;
}
}
甚至在 Generic Class 中再內含一個 Generic Class
public class WrapperFoo<T> {
//之前宣告的 Generic Class
private GenericFoo<T> foo;

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

public GenericFoo<T> getFoo() {
return foo;
}
}


Generic(泛型) 的進階用法

以上所介紹的都是 Generics 的基本用法,然而 Generics 的用法不僅如此,以下介紹三種進階的用法:

限制 Generic 可用型態

定義 Generic Class 時,預設可以用任何型態來 instantiate generic class;但其實是可以限制在特定的型態的,以下用個範例來說明:
import java.util.List;

//表示僅能使用由 List 或其衍生出來的型態
//用其他非 List 衍生出來的型態,compile 時會發生錯誤
public class ListGenericFoo<T extends List> {
private T[] fooArray;

public void setFooArray(T[] fooArray) {
this.fooArray = fooArray;
}

public T[] getFooArray() {
return fooArray;
}
}
如上述,透過 ListGenericFoo 所產生的 object,僅能使用 List 或其衍生出來的型態,使用其他則在編譯時會發生錯誤。

【註】由此可知,其實「<T>」與「<T extends Object>」是相同的

Wildcard

假設有個 GenericFoo class,使用下列方式宣告:
GenericFoo<Integer> foo1 = null;
GenericFoo<Boolean> foo2 = null;
如此一來,foo1 就只能參考到由 GenericFoo<Interger> 所產生出來的物件。

但若希望達成下面的功能,應該要如何作呢?
foo = new GenericFoo<ArrayList>();
foo = new GenericFoo<LinkedList>();
答案就是使用 Wildcard,以下用範例程式來說明:

GenericWildcard.java
public class GenericWildcard<T> {
private T foo;

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

public T getFoo() {
return foo;
}
}
GenericWildcardDemo.java
import java.util.*;

public class GenericWildcardDemo {
public static void main(String[] args) {
GenericWildcard<? extends List> foo = null;

//成功參考到 ArrayList 型態
foo = new GenericWildcard<ArrayList>();
System.out.println(foo.toString());

//成功參考到 LinkedList 型態
foo = new GenericWildcard<LinkedList>();
System.out.println(foo.toString());

//產生編譯錯誤,String不屬於 List 擴充出來的型態
foo = new GenericWildcard<String>();
}
}

Extends Generic Class

最後,還可以將原先定義好的 Generic Class 進行擴充,不過在擴充時建議保留 parent class 的型態持有者,也就是範例程式中的 T1、T2:

GenericFoo4.java
public class GenericFoo4<T1, T2> {
private T1 foo1;
private T2 foo2;

public void setFoo1(T1 foo1) {
this.foo1 = foo1;
}

public T1 getFoo1() {
return foo1;
}

public void setFoo2(T2 foo2) {
this.foo2 = foo2;
}

public T2 getFoo2() {
return foo2;
}
}
SubGenericFoo4.java
public class SubGenericFoo4<T1, T2, T3> extends GenericFoo4<T1, T2> {
private T3 foo3;

public void setFoo3(T3 foo3) {
this.foo3 = foo3;
}

public T3 getFoo3() {
return foo3;
}
}

因此可以了解,Generics 在使用上的彈性是相當大的,透過 Generics 的機制,讓原本需要透過 polymorphism 才能達成的功能就輕鬆達成了! 而 compiler 還可以協助檢查錯誤! 如果使用得宜,的確是一個相當好用的功能。

沒有留言:

張貼留言