JDK5.0新特性的學習--泛型(下)

xuehongliang發表於2007-06-29
沒有引數的情況下使用泛型
既然在J2SE 5.0中收集型別已經泛型化,那麼,原來的使用這些型別的程式碼將如何呢?很幸運,他們在JAVA 5中將繼續工作,因為你能使用沒有引數的泛型。比如,你能繼續像原來一樣使用List介面,正如下面的例子一樣。

List stringList1 = new ArrayList();
stringList1.add("Java 1.0 - 5.0");
stringList1.add("without generics");
String s1 = (String) stringList1.get(0);
一個沒有任何引數的泛型被稱為原型(raw type)。它意味著這些為JDK1.4或更早的版本而寫的程式碼將繼續在java 5中工作。

儘管如此,一個需要注意的事情是,JDK5編譯器希望你使用帶引數的泛型。否則,編譯器將提示警告,因為他認為你可能忘了定義型別變數s。比如,編譯上面的程式碼的時候你會看到下面這些警告,因為第一個List被認為是原型。

Note: com/brainysoftware/jdk5/app16/GenericListTest.java
uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

當你使用原型時,如果你不想看到這些警告,你有幾個選擇來達到目的:
1.編譯時帶上引數-source 1.4
2.使用@SupressWarnings("unchecked")註釋
3.更新你的程式碼,使用List. List的例項能接受任何型別的物件,就像是一個原型List。然而,編譯器不會報錯。

使用 ? 萬用字元
前面提過,如果你宣告瞭一個List, 那麼這個List對aType起作用,所以你能儲存下面這些型別的物件:
1.一個aType的例項
2.它的子類的例項(如果aType是個類)
3.實現aType介面的類例項(如果aType是個介面)
但是,請注意,一個泛型本身是個JAVA型別,就像java.lang.String或java.io.File一樣。傳遞不同的型別變數給泛型可以建立不同的JAVA型別。比如,下面例子中list1和list2引用了不同的型別物件。

List
list1 = new ArrayList();
List list2 = new ArrayList();
 
list1指向了一個型別變數s為java.lang.Objects 的List而list2指向了一個型別變數s為String 的List。所以傳遞一個List給一個引數為List的函式將導致compile time錯誤。下面列表可以說明:

public class AllowedTypeTest {
public static void doIt(Listl) {
}
public static void main(String[] args) {
List myList = new ArrayList();
// 這裡將產生一個錯誤
doIt(myList);
}
}
上面的程式碼無法編譯,因為你試圖傳遞一個錯誤的型別給函式doIt。doIt的引數是List二你傳遞的引數是List
可以使用 ? 萬用字元解決這個難題。List 意味著一個對任何物件起作用的List。所以,doIt可以改為:

public static void doIt(List l) {}

在某些情況下你會考慮使用 ? 萬用字元。比如,你有一個printList函式,這個函式列印一個List的所有成員,你想讓這個函式對任何型別的List起作用時。否則,你只能累死累活的寫很多printList的過載函式。下面的列表引用了使用 ? 萬用字元的printList函式。

public class WildCardTest {

public static void printList(List list) {
for (Object element : list) {
System.out.println(element);
}
}
public static void main(String[] args) {
List list1 = new ArrayList();
list1.add("Hello");
list1.add("World");
printList(list1);

List list2 = new ArrayList();
list2.add(100);
list2.add(200);
printList(list2);
}
}

這些程式碼說明了在printList函式中,List表示各種型別的List物件。然而,請注意,在宣告的時候使用 ? 萬用字元是不合法的,像這樣:

List myList = new ArrayList(); // 不合法

如果你想建立一個接收任何型別物件的List,你可以使用Object作為型別變數,就像這樣:

List
myList = new ArrayList();
 
在函式中使用界限萬用字元
在之前的章節中,你學會了透過傳遞不同的型別變數s來建立不同JAVA型別的泛型,但並不考慮型別變數s之間的繼承關係。在很多情況下,你想一個函式有不同的List引數。比如,你有一個函式getAverage,他返回了一個List中成員的平均值。然而,如果你把List作為getAverage的引數,你就沒法傳遞List 或List引數,因為List和List 和List不是同樣的型別。

你能使用原型或使用萬用字元,但這樣無法在編譯時進行安全型別檢查,因為你能傳遞任何型別的List,比如List的例項。你可以使用List作為引數,但是你就只能傳遞List給函式。但這樣就使你的函式功能減少,因為你可能更多的時候要操作List或List,而不是List

J2SE5.0增加了一個規則來解決了這種約束,這個規則就是允許你定義一個上界(upper bound) 型別變數.在這種方式中,你能傳遞一個型別或它的子類。在上面getAverage函式的例子中,你能傳遞一個List或它的子類的例項,比如List or List

使用上界規則的語法這麼定義的:GenericType<!--xtends upperBoundTy--&gt. 比如,對getAverage函式的引數,你可以這麼寫List<!--xtends Numb--&gt. 下面例子說明了如何使用這種規則。
public class BoundedWildcardTest {
public static double getAverage(List<!--xtends Numb--&gt numberList)
{
double total = 0.0;
for (Number number : numberList)
total += number.doubleValue();
return total/numberList.size();
}

public static void main(String[] args) {
List integerList = new ArrayList();
integerList.add(3);
integerList.add(30);
integerList.add(300);
System.out.println(getAverage(integerList)); // 111.0
List doubleList = new ArrayList();
doubleList.add(3.0);
doubleList.add(33.0);
System.out.println(getAverage(doubleList)); // 18.0
}
}
由於有了上界規則,上面例子中的getAverage函式允許你傳遞一個List 或一個型別變數是任何java.lang.Number子類的List。

下界規則
關鍵字extends定義了一個型別變數的上界。透過使用super關鍵字,我們可以定義一個型別變數的下界,儘管使用的情況不多。比如,如果一個函式的引數是List<!--uper Integ--&gt,那麼意味著你可以傳遞一個List的例項或者任何java.lang.Integer的超類(superclass)。

建立泛型

前面的章節主要說明了如何使使用泛型,特別是集合框架中的類。現在我們開始學習如何寫自己的泛型。

基本上,除了宣告一些你想要使用的型別變數s外,一個泛型和別的類沒有什麼區別。這些型別變數s位於型別後面的<>中。比如,下面的Point就是個泛型。一個Point物件代表了一個系統中的點,它有橫座標和縱座標。透過使Point泛型化,你能定義一個點例項的精確程度。比如,如果一個Point物件需要非常精確,你就把Double作為型別變數。否則,Integer 就夠了。
package com.brainysoftware.jdk5.app16;
public class Point {
T x;
T y;
public Point(T x, T y) {
this.x = x;
this.y = y;
}
public T getX() {
return x;
}
public T getY() {
return y;
}
public void setX(T x) {
this.x = x;
}
public void setY(T y) {
this.y = y;
}
}
在這個例子中,T是Point的型別變數 。T是getX和getY的返回值型別,也是setX和setY的引數型別。此外,建構函式結合兩個T引數。
使用point類就像使用別的類一樣。比如,下面的例子建立了兩個Point物件:ponint1和point2。前者把Integer作為型別變數,而後者把Double作為型別變數。

Point point1 = new Point(4, 2);
point1.setX(7);
Point point2 = new Point(1.3, 2.6);
point2.setX(109.91);
總結
泛型使程式碼在編譯時有了更嚴格的型別檢查。特別是在集合框架中,泛型有兩個作用。第一,他們增加了對集合型別在編譯時的型別檢查,所以集合類所能持有的型別對傳遞給它的引數型別起了限制作用。比如你建立了一個持有strings的java.util.List例項,那麼他就將不能接受Integers或別的型別。其次,當你從一個集合中取得一個元素時,泛型消除了型別轉換的必要。
泛型能夠在沒有型別變數的情況下使用,比如,作為原型。這些措施讓Java 5之前的程式碼能夠執行在JRE 5中。但是,對新的應用程式,你最好不要使用原型,因為以後Java可能不支援他們。

你已經知道透過傳遞不同型別的型別變數給泛型可以產生不同的JAVA型別。就是說List和List的型別是不同的。儘管String是java.lang.Object。但是傳遞一個List給一個引數是List的函式會引數會產生編譯錯誤(compile error)。函式能用 ? 萬用字元使其接受任何型別的引數。List 意味著任何型別的物件。
最後,你已經看到了寫一個泛型和別的一般JAVA類沒有什麼區別。你只需要在型別名稱後面的<>中宣告一系列的型別變數s就行了。這些型別變數s就是返回值型別或者引數型別。根據慣例,一個型別變數用一個大寫字母表示。[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/800861/viewspace-922110/,如需轉載,請註明出處,否則將追究法律責任。

相關文章