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

xuehongliang發表於2007-06-29

沒有泛型的日子
所有的java類都源自java.lang.Object,這意味著所有的JAVA物件能轉換成Object。因此,在之前的JDK的版本中,很多集合框架的函式接受一個Object引數。所以,collections是一個能持有任何物件的多用途工具,但帶來了不良的後果。

舉個簡單的例子,在JDK 5.0的之前版本中,類List的函式add接受一個Object引數:

  1. public boolean add(java.lang.Object element)


所以你能傳遞任何型別給add。這是故意這麼設計的。否則,它只能傳遞某種特定的物件,這樣就會出現各種List型別,如,StringList, EmployeeList, AddressList等。
add透過Object傳遞能帶來好處,現在我們考慮get函式(返回List中的一個元素).如下是JDK 5之前版本的定義:

  1. public java.lang.Object get(int index) throws IndexOutOfBoundsException

get返回一個Object.不幸的事情從此開始了.假如你儲存了兩個String物件在一個List中:

  1. List stringList1 = new ArrayList();
  2. stringList1.add("Java 5");
  3. stringList1.add("with generics");

當你想從stringList1取得一個元素時,你得到了一個Object.為了操作原來的型別元素,你不得不把它轉換為String。

  1. String s1 = (String) stringList1.get(0);

但是,假如你曾經把一個非String物件加入stringList1中,上面的程式碼會丟擲一個ClassCastException. 有了泛型,你能建立一個單一用途的List例項.比如,你能建立一個只接受String物件的List例項,另外一個例項只能接受Employee物件.這同樣適用於集合框架中的其他型別.

泛型入門

像一個函式能接受引數一樣,一個泛型也能接受引數.這就是一個泛型經常被稱為一個引數化型別的原因.但是不像函式用()傳遞引數,泛型是用<>傳遞引數的.宣告一個泛型和宣告一個普通類沒有什麼區別,只不過你把泛型的變數放在<>中.
比如,在JDK 5中,你可以這樣宣告一個java.util.List : List myList;
E 稱為型別變數.意味著一個變數將被一個型別替代.替代型別變數的值將被當作引數或返回型別.對於List介面來說,當一個例項被建立以後,E 將被當作一個add或別的函式的引數.E 也會使get或別的引數的返回值.下面是add和get的定義:

  1. boolean add E get(int index)

一個泛型在宣告或例示時允許你傳遞特定的型別變數: E.除此之外,如果E是個類,你可以傳遞子類;如果E是個介面,你可以傳遞實現介面的類;

  1. List numberList= new ArrayList();
  2. numberList.add(2.0);
  3. numberList.add(2);

-----------------------------譯者新增--------------------

那麼mylist的add函式將接受一個String作為他的引數,而get函式將返回一個String.因為返回了一個特定的型別,所以不用型別轉化了。

根據慣例,我們使用一個唯一的大寫字目表示一個型別變數。為了建立一個泛型,你需在宣告時傳遞同樣的引數列表。比如,你要想建立一個ArrayList來操作String ,你必須把String放在<>中。如:

  1. List myList = new ArrayList();

再比如,java.util.Map 是這麼定義的:

public interface Map

K用來宣告map鍵(KEY)的型別而V用來表示值(VALUE)的型別。put和values是這麼定義的:

V put(K key, V value)
Collection values()

一個泛型不準直接的或間接的是java.lang.Throwable的子類。因為異常是在執行時丟擲的,所以它不可能預言什麼型別的異常將在編譯時丟擲.
列表1的例子將比較List在JDK 1.4 和JDK1.5的不同

  1. public class GenericListTest {
  2. public static void main(String[] args) {
  3. // in JDK 1.4
  4. List stringList1 = new ArrayList();
  5. stringList1.add("Java 1.0 - 5.0");
  6. stringList1.add("without generics");
  7. // cast to java.lang.String
  8. String s1 = (String) stringList1.get(0);
  9. System.out.println(s1.toUpperCase());
  10. // now with generics in JDK 5
  11. List stringList2 = new ArrayList();
  12. stringList2.add("Java 5.0");
  13. stringList2.add("with generics");
  14. // no need for type casting
  15. String s2 = stringList2.get(0);
  16. System.out.println(s2.toUpperCase());
  17. }
  18. }

在列表1中,stringList2是個泛型。宣告List告訴編譯器List的例項能接受一個String物件。當然,在另外的情況中,你能新建能接受各種物件的List例項。注意,當從List例項中返回成員元素時,不需要物件轉化,因為他返回的了你想要的型別,也就是String.

泛型的型別檢查(type checking)是在編譯時完成的.
最讓人感興趣的事情是,一個泛型是個型別並且能被當作一個型別變數。比如,你想你的List儲存lists of Strings,你能透過把List作為他的型別變數來宣告List。比如:

  1. List> myListOfListsOfStrings;

要從myList中的第一個List重新取得String,你可以這麼用:

  1. String s = myListOfListsOfStrings.get(0).get(0);

下一個列表中的ListOfListsTest類示範了一個List(命名為listOfLists)接受一個String List作為引數。

  1. public class ListOfListsTest {
  2. public static void main(String[] args) {
  3. List listOfStrings = new ArrayList();
  4. listOfStrings.add("Hello again");
  5. List> listOfLists = new ArrayList>();
  6. listOfLists.add(listOfStrings);
  7. String s = listOfLists.get(0).get(0);
  8. System.out.println(s); // prints "Hello again"
  9. }
  10. }


另外,一個泛型接受一個或多個型別變數。比如,java.util.Map有兩個型別變數s。第一個定義了鍵(key)的型別,第二個定義了值(value)的型別。下面的例子講教我們如何使用個一個泛型Map.

  1. public class MapTest {
  2. public static void main(String[] args) {
  3. Map map = new HashMap();
  4. map.put("key1", "value1");
  5. map.put("key2", "value2");
  6. String value1 = map.get("key1");
  7. }
  8. }


在這個例子中,重新得到一個key1代表的String值,我們不需要任何型別轉換。



-----------------------------譯者新增--------------------

如果你傳遞一個String給一個List,比如:

List myList;[@more@]

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

相關文章