[轉]多角度看 Java 中的泛型

[轉]多角度看 Java 中的泛型

級別: 初級

周 晶 ([email protected]), 計算機碩士

2006 年 12 月 28 日

泛型是 Sun 公司發佈的 JDK 5.0 中的一個重要特性,它的最大優點是提供了程序的類型安全同可以向後兼容。爲了幫助讀者更好地理解和使用泛型,本文通過一些示例從基本原理,重要概念,關鍵技術,以及相似技術比較等多個角度對 Java 語言中的泛型技術進行了介紹,重點強調了泛型中的一些基本但又不是很好理解的概

引言

很多 Java 程序員都使用過集合(Collection),集合中元素的類型是多種多樣的,例如,有些集合中的元素是 Byte 類型的,而有些則可能是 String 類型的,等等。Java 語言之所以支持這麼多種類的集合,是因爲它允許程序員構建一個元素類型爲 Object 的 Collection,所以其中的元素可以是任何類型。

當使用 Collection 時,我們經常要做的一件事情就是要進行類型轉換,當轉換成所需的類型以後,再對它們進行處理。很明顯,這種設計給編程人員帶來了極大的不便,同時也容易引入錯誤。

在很多 Java 應用中,上述情況非常普遍,爲了解決這個問題,使 Java 語言變得更加安全好用,近些年的一些編譯器對 Java 語言進行了擴充,使 Java 語言支持了"泛型",特別是 Sun 公司發佈的 JDK 5.0 更是將泛型作爲其中一個重要的特性加以推廣。

本文首先對泛型的基本概念和特點進行簡單介紹,然後通過引入幾個實例來討論帶有泛型的類,泛型中的子類型,以及範化方法和受限類型參數等重要概念。爲了幫助讀者更加深刻的理解並使用泛型,本文還介紹了泛型的轉化,即,如何將帶有泛型的 Java 程序轉化成一般的沒有泛型的 Java 程序。這樣,讀者對泛型的理解就不會僅僅侷限在表面上了。考慮到多數讀者僅僅是使用泛型,因此本文並未介紹泛型在編譯器中的具體實現。Java 中的泛型和 C++ 中的模板表面上非常相似,但實際上二者還是有很大區別的,本文最後簡單介紹了 Java 中的泛型與 C++ 模板的主要區別。

泛型概覽

泛型本質上是提供類型的"類型參數",它們也被稱爲參數化類型(parameterized type)或參量多態(parametric polymorphism)。其實泛型思想並不是 Java 最先引入的,C++ 中的模板就是一個運用泛型的例子。

GJ(Generic Java)是對 Java 語言的一種擴展,是一種帶有參數化類型的 Java 語言。用 GJ 編寫的程序看起來和普通的 Java 程序基本相同,只不過多了一些參數化的類型同時少了一些類型轉換。實際上,這些 GJ 程序也是首先被轉化成一般的不帶泛型的 Java 程序後再進行處理的,編譯器自動完成了從 Generic Java 到普通 Java 的翻譯。具體的轉化過程大致分爲以下幾個部分:

  • 將參數化類型中的類型參數"擦除"(erasure)掉;
  • 將類型變量用"上限(upper bound)"取代,通常情況下這些上限是 Object。這裏的類型變量是指實例域,本地方法域,方法參數以及方法返回值中用來標記類型信息的"變量",例如:實例域中的變量聲明 A elem;,方法聲明 Node (A elem){};,其中,A 用來標記 elem 的類型,它就是類型變量。
  • 添加類型轉換並插入"橋方法"(bridge method),以便覆蓋(overridden)可以正常的工作。

轉化後的程序和沒有引入泛型時程序員不得不手工完成轉換的程序是非常一致的,具體的轉化過程會在後面介紹。GJ 保持了和 Java 語言以及 Java 虛擬機很好的兼容性,下面對 GJ 的特點做一個簡要的總結。

  • 類型安全。 泛型的一個主要目標就是提高 Java 程序的類型安全。使用泛型可以使編譯器知道變量的類型限制,進而可以在更高程度上驗證類型假設。如果沒有泛型,那麼類型的安全性主要由程序員來把握,這顯然不如帶有泛型的程序安全性高。
  • 消除強制類型轉換。泛型可以消除源代碼中的許多強制類型轉換,這樣可以使代碼更加可讀,並減少出錯的機會。
  • 向後兼容。支持泛型的 Java 編譯器(例如 JDK5.0 中的 Javac)可以用來編譯經過泛型擴充的 Java 程序(GJ 程序),但是現有的沒有使用泛型擴充的 Java 程序仍然可以用這些編譯器來編譯。
  • 層次清晰,恪守規範。無論被編譯的源程序是否使用泛型擴充,編譯生成的字節碼均可被虛擬機接受並執行。也就是說不管編譯器的輸入是 GJ 程序,還是一般的 Java 程序,經過編譯後的字節碼都嚴格遵循《Java 虛擬機規範》中對字節碼的要求。可見,泛型主要是在編譯器層面實現的,它對於 Java 虛擬機是透明的。
  • 性能收益。目前來講,用 GJ 編寫的代碼和一般的 Java 代碼在效率上是非常接近的。 但是由於泛型會給 Java 編譯器和虛擬機帶來更多的類型信息,因此利用這些信息對 Java 程序做進一步優化將成爲可能。

以上是泛型的一些主要特點,下面通過幾個相關的例子來對 Java 語言中的泛型進行說明。

帶有泛型的類

爲了幫助大家更好地理解 Java 語言中的泛型,我們在這裏先來對比兩段實現相同功能的 GJ 代碼和 Java 代碼。通過觀察它們的不同點來對 Java 中的泛型有個總體的把握,首先來分析一下不帶泛型的 Java 代碼,程序如下:

1 interface Collection {
2    public void add (Object x);
3 public Iterator iterator ();
4 }
5
6 interface Iterator {
7    public Object next ();
8 public boolean hasNext ();
9 }
10
11 class NoSuchElementException extends RuntimeException {}
12
13 class LinkedList implements Collection {
14
15 protected class Node {
16    Object elt;
17 Node next = null;
18 Node (Object elt) { this.elt = elt; }
19 }
20
21 protected Node head = null, tail = null;
22
23 public LinkedList () {}
24
25 public void add (Object elt) {
26 if (head == null) { head = new Node(elt); tail = head; }
27 else { tail.next = new Node(elt); tail = tail.next; }
28 }
29
30 public Iterator iterator () {
31
32 return new Iterator () {
33 protected Node ptr = head;
34 public boolean hasNext () { return ptr != null; }
35 public Object next () {
36 if (ptr != null) {
37 Object elt = ptr.elt; ptr = ptr.next; return elt;
38 } else throw new NoSuchElementException ();
39 }
40 };
41 }
42 }

接口 Collection 提供了兩個方法,即添加元素的方法 add(Object x),見第 2 行,以及返回該 CollectionIterator 實例的方法 iterator(),見第 3 行。Iterator 接口也提供了兩個方法,其一就是判斷是否有下一個元素的方法 hasNext(),見第 8 行,另外就是返回下一個元素的方法 next(),見第 7 行。LinkedList 類是對接口 Collection 的實現,它是一個含有一系列節點的鏈表,節點中的數據類型是 Object,這樣就可以創建任意類型的節點了,比如 Byte, String 等等。上面這段程序就是用沒有泛型的傳統的 Java 語言編寫的代碼。接下來我們分析一下傳統的 Java 語言是如何使用這個類的。

代碼如下:

1 class Test {
2     public static void main (String[] args) {
3 // byte list
4 LinkedList xs = new LinkedList();
5 xs.add(new Byte(0)); xs.add(new Byte(1));
6 Byte x = (Byte)xs.iterator().next();
7 // string list
8 LinkedList ys = new LinkedList();
9 ys.add("zero"); ys.add("one");
10 String y = (String)ys.iterator().next();
11 // string list list
12 LinkedList zss = new LinkedList();
13 zss.add(ys);
14 String z = (String)((LinkedList)zss.iterator().next()).iterator().next();
15 // string list treated as byte list
16 Byte w = (Byte)ys.iterator().next(); // run-time exception
17 }
18 }

從上面的程序我們可以看出,當從一個鏈表中提取元素時需要進行類型轉換,這些都要由程序員顯式地完成。如果我們不小心從 String 類型的鏈表中試圖提取一個 Byte 型的元素,見第 15 到第 16 行的代碼,那麼這將會拋出一個運行時的異常。請注意,上面這段程序可以順利地經過編譯,不會產生任何編譯時的錯誤,因爲編譯器並不做類型檢查,這種檢查是在運行時進行的。不難發現,傳統 Java 語言的這一缺陷推遲了發現程序中錯誤的時間,從軟件工程的角度來看,這對軟件的開發是非常不利的。接下來,我們討論一下如何用 GJ 來實現同樣功能的程序。源程序如下:

1 interface Collection<A> {
2 public void add(A x);
3 public Iterator<A> iterator();
4 }
5
6 interface Iterator<A> {
7 public A next();
8 public boolean hasNext();
9 }
10
11 class NoSuchElementException extends RuntimeException {}
12
13 class LinkedList<A> implements Collection<A> {
14 protected class Node {
15 A elt;
16 Node next = null;
17 Node (A elt) { this.elt = elt; }
18 }
19
20 protected Node head = null, tail = null;
21
22 public LinkedList () {}
23
24 public void add (A elt) {
25 if (head == null) { head = new Node(elt); tail = head; }
26 else { tail.next = new Node(elt); tail = tail.next; }
27 }
28
29 public Iterator<A> iterator () {
30 return new Iterator<A> () {
31 protected Node ptr = head;
32 public boolean hasNext () { return ptr != null; }
33 public A next () {
34 if (ptr != null) {
35 A elt = ptr.elt; ptr = ptr.next; return elt;
36 } else throw new NoSuchElementException ();
37 }
38 };
39 }
40 }

程序的功能並沒有任何改變,只是在實現方式上使用了泛型技術。我們注意到上面程序的接口和類均帶有一個類型參數 A,它被包含在一對尖括號(< >)中,見第 1,6 和 13 行,這種表示法遵循了 C++ 中模板的表示習慣。這部分程序和上面程序的主要區別就是在 Collection, Iterator, 或 LinkedList 出現的地方均用 Collection<A>, Iterator<A>, 或 LinkedList<A> 來代替,當然,第 22 行對構造函數的聲明除外。

下面再來分析一下在 GJ 中是如何對這個類進行操作的,程序如下:

1 class Test {
2 public static void main (String [] args) {
3 // byte list
4 LinkedList<Byte> xs = new LinkedList<Byte>();
5 xs.add(new Byte(0)); xs.add(new Byte(1));
6 Byte x = xs.iterator().next();
7 // string list
8 LinkedList<String> ys = new LinkedList<String>();
9 ys.add("zero"); ys.add("one");
10 String y = ys.iterator().next();
11 // string list list
12 LinkedList<LinkedList<String>>zss=
newLinkedList<LinkedList<String>>();
13 zss.add(ys);
14 String z = zss.iterator().next().iterator().next();
15 // string list treated as byte list
16 Byte w = ys.iterator().next(); // compile-time error
17 }
18 }

在這裏我們可以看到,有了泛型以後,程序員並不需要進行顯式的類型轉換,只要賦予一個參數化的類型即可,見第 4,8 和 12 行,這是非常方便的,同時也不會因爲忘記進行類型轉換而產生錯誤。另外需要注意的就是當試圖從一個字符串類型的鏈表裏提取出一個元素,然後將它賦值給一個 Byte 型的變量時,見第 16 行,編譯器將會在編譯時報出錯誤,而不是由虛擬機在運行時報錯,這是因爲編譯器會在編譯時刻對 GJ 代碼進行類型檢查,此種機制有利於儘早地發現並改正錯誤。

類型參數的作用域是定義這個類型參數的整個類,但是不包括靜態成員函數。這是因爲當訪問同一個靜態成員函數時,同一個類的不同實例可能有不同的類型參數,所以上述提到的那個作用域不應該包括這些靜態函數,否則就會引起混亂。

泛型中的子類型

在 Java 語言中,我們可以將某種類型的變量賦值給其父類型所對應的變量,例如,String 是 Object 的子類型,因此,我們可以將 String 類型的變量賦值給 Object 類型的變量,甚至可以將 String [ ] 類型的變量(數組)賦值給 Object [ ] 類型的變量,即 String [ ] 是 Object [ ] 的子類型。

上述情形恐怕已經深深地印在了廣大讀者的腦中,對於泛型來講,上述情形有所變化,因此請廣大讀者務必引起注意。爲了說明這種不同,我們還是先來分析一個小例子,代碼如下所示:

1 List<String> ls = new ArrayList<String>();
2 List<Object> lo = ls;
3 lo.add(new Integer());
4 String s = ls.get(0);

上述代碼的第二行將 List<String> 賦值給了 List<Object>,按照以往的經驗,這種賦值好像是正確的,因爲 List<String> 應該是 List<Object> 的子類型。這裏需要特別注意的是,這種賦值在泛型當中是不允許的!List<String> 也不是 List<Object> 的子類型。

如果上述賦值是合理的,那麼上面代碼的第三行的操作將是可行的,因爲 loList<Object>,所以向其添加 Integer 類型的元素應該是完全合法的。讀到此處,我們已經看到了第二行的這種賦值所潛在的危險,它破壞了泛型所帶來的類型安全性。

一般情況下,如果 A 是 B 的子類型,C 是某個泛型的聲明,那麼 C<A> 並不是 C<B> 的子類型,我們也不能將 C<A> 類型的變量賦值給 C<B> 類型的變量。這一點和我們以前接觸的父子類型關係有很大的出入,因此請讀者務必引起注意。

泛化方法和受限類型參數

在這一部分我們將討論有關泛化方法(generic method )和受限類型參數(bounded type parameter)的內容,這是泛型中的兩個重要概念,還是先來分析一下與此相關的代碼。

1 interface Comparable<A> {
2 public int compareTo(A that);
3 }
4
5 class Byte implements Comparable<Byte> {
6 private byte value;
7 public Byte(byte value) {this.value = value;}
8 public byte byteValue() {return value;}
9 public int compareTo(Byte that) {
10 return this.value - that.value;
11 }
12 }
13
14 class Collections {
15 public static <A implements Comparable<A>>
16             A max (Collection<A> xs) {
17     Iterator<A> xi = xs.iterator();
18     A w = xi.next();
19     while (xi.hasNext()) {
20 A x = xi.next();
21 if (w.compareTo(x) < 0) w = x;
22 }
23 return w;
24 }
25 }

這裏定義了一個接口 Comparable<A>,用來和 A 類型的對象進行比較。類 Byte 實現了這個接口,並以它自己作爲類型參數,因此,它們自己就可以和自己進行比較了。

第 14 行到第 25 行的代碼定義了一個類 Collections,這個類包含一個靜態方法 max(Collection<A> xs),它用來在一個非空的 Collection 中尋找最大的元素並返回這個元素。這個方法的兩個特點就是它是一個泛化方法並且有一個受限類型參數。

之所以說它是泛化了的方法,是因爲這個方法可以應用到很多種類型上。當要將一個方法聲明爲泛化方法時,我們只需要在這個方法的返回類型(A)之前加上一個類型參數(A),並用尖括號(< >)將它括起來。這裏的類型參數(A)是在方法被調用時自動實例化的。例如,假設對象 m 的類型是 Collection<Byte>,那麼當使用下面的語句:

Byte x = Collections.max(m);

調用方法 max 時,該方法的參數 A 將被推測爲 Byte。

根據上面討論的內容,泛化方法 max 的完整聲明應該是下面的形式:

< A > A max (Collection<A> xs) { max 的方法體 }


但是,我們見到的 max 在 < A > 中還多了 "implements Comparable<A>" 一項,這是什麼呢?這就是我們下面將要談到的"受限的類型參數"。在上面的例子中,

類型參數 A 就是一個受限的的類型參數,因爲它不是泛指任何類型,而是指那些自己和自己作比較的類型。例如參數可以被實例化爲 Byte,因爲程序中有 Byte implements Comparable<Byte> 的語句,參見第 5 行。這種限制(或者說是範圍)通過如下的方式表示,"類型參數 implements 接口",或是 "類型參數 extend 類",上面程序中的"Byte implements Comparable<Byte>"就是一例。

泛型的轉化

在前面的幾部分內容當中,我們介紹了有關泛型的基礎知識,到此讀者對 Java 中的泛型技術應該有了一定的瞭解,接下來的這部分內容將討論有關泛型的轉化,即如何將帶有泛型的 Java 代碼轉化成一般的沒有泛型 Java 代碼。其實在前面的部分裏,我們或多或少地也提到了一些相關的內容,下面再來詳細地介紹一下。

首先需要明確的一點是上面所講的這種轉化過程是由編譯器(例如:Javac)完成的,虛擬機並不負責完成這一任務。當編譯器對帶有泛型的 Java 代碼進行編譯時,它會去執行類型檢查和類型推斷,然後生成普通的不帶泛型的字節碼,這種字節碼可以被一般的Java虛擬機接收並執行,這種技術被稱爲擦除(erasure)。

可見,編譯器可以在對源程序(帶有泛型的 Java 代碼)進行編譯時使用泛型類型信息保證類型安全,對大量如果沒有泛型就不會去驗證的類型安全約束進行驗證,同時在生成的字節碼當中,將這些類型信息清除掉。

對於不同的情況,擦除技術所執行的"擦除"動作是不同的,主要分爲以下幾種情況:

  • 對於參數化類型,需要刪除其中的類型參數,例如,LinkedList<A> 將被"擦除"爲 LinkedList;
  • 對於非參數化類型,不作擦除,或者說用它自己來擦除自己,例如 String 將被"擦除"爲 String;
  • 對於類型變量(有關類型變量的說明請參考"泛型概覽"相關內容),要用它們的上限來對它們進行替換。多數情況下這些上限是 Object,但是也有例外,後面的部分將會對此進行介紹。

除此之外,還需要注意的一點是,在某些情況下,擦除技術需要引入類型轉換(cast),這些情況主要包括:

情況 1. 方法的返回類型是類型參數;

情況 2. 在訪問數據域時,域的類型是一個類型參數。

例如在本文"帶有泛型的類"一小節的最後,我們給出了一段測試程序,一個 Test 類。這個類包含以下幾行代碼:

8 LinkedList<String> ys = new LinkedList<String>();
9 ys.add("zero"); ys.add("one");
10 String y = ys.iterator().next();

這部分代碼轉換後就變成了如下的代碼:

8 LinkedList ys = new LinkedList();
9 ys.add("zero"); ys.add("one");
10 String y = (String)ys.iterator().next();

第 10 行的代碼進行了類型轉換,這是因爲在調用 next() 方法時,編譯器發現該方法的返回值類型是類型參數 A(請參見對方法 next() 的定義),因此根據上面提到的情況 1,需要進行類型轉換。

上面介紹了泛型轉化中的擦除技術,接下來,我們討論一下泛型轉化中的另外一個重要問題--橋方法(bridge method)。

Java 是一種面向對象的語言,因此覆蓋(overridden)是其中的一項重要技術。覆蓋能夠正常"工作"的前提是方法名和方法的參數類型及個數完全匹配(參數的順序也應一致),爲了滿足這項要求,編譯器在泛型轉化中引入了橋方法(bridge method)。接下來,我們通過一個例子來分析一下橋方法在泛型轉化中所起的作用。在本文"泛化方法和受限類型參數"一小節所給出的代碼中,第 9 行到第 11 行的程序如下所示:

public int compareTo(Byte that)
{ return this.value - that.value; }

這部分代碼經過轉化,就變成了下面的樣子:

public int compareTo(Byte that)
{ return this.value - that.value; }
public int compareTo(Object that)
{ return this.compareTo((Byte)that); }

第 12 行的方法 compareTo(Object that) 就是一個橋方法,在這裏引入這個方法是爲了保證覆蓋能夠正常的發生。我們在前面提到過,覆蓋必須保證方法名和參數的類型及數目完全匹配,在這裏通過引入這個"橋"即可達到這一目的,由這個"橋"進行類型轉換,並調用第 9 行參數類型爲 Byte 的方法 compareTo(Byte that),需要注意的一點是這裏的 "Object" 也並不一定是完全匹配的類型,但由於它是 Java 語言中類層次結構的根,所以這裏用 "Object" 可以接受其他任何類型的參數。

根據面向對象的基本概念,我們知道,重載(overloading)允許橋方法和原來的方法共享同一個方法名,正如上面例子所顯示的那樣,因此橋方法的引入是完全合法的。一般情況下,當一個類實現了一個參數化的接口或是繼承了一個參數化的類時,需要引入橋方法。

到此,我們對泛型中的子類型,帶有泛型的類,泛化方法,受限類型參數以及泛型的轉化進行了簡要的介紹,下面部分將結合這些技術對前面提到的例子進行一下總結,以便能夠幫助讀者更深刻更全面地理解泛型。

首先來分析一下本文提到的那個 Collection 的例子。這裏先是定義了兩個接口 CollectionIterator,然後又定義了一個對接口 Collection 的一個實現 LinkedList。根據上面所介紹的對泛型的轉化過程,這段代碼轉化後的 Java 程序爲:

interface Collection
{
public void add (Object x);
public Iterator iterator ();
}
interface Iterator
{
public Object next ();
public boolean hasNext (); 9
}
class NoSuchElementException extends RuntimeException {}
class LinkedList implements Collection
{
protected class Node
{ Object elt;
Node next = null;
Node (Object elt)
{ this.elt = elt; }
}
protected Node head = null, tail = null;
public LinkedList () {}
public void add (Object elt)
{
if (head == null)
{
head = new Node(elt);
tail = head;
} else
{
tail.next = new Node(elt);
tail = tail.next;
}
}
public Iterator iterator ()
{
return new Iterator ()
{
protected Node ptr = head;
public boolean hasNext ()
{ return ptr != null; }
public Object next ()
{
if (ptr != null)
{
Object elt = ptr.elt;
ptr = ptr.next;
return elt;
} else
{
throw new NoSuchElementException ();
}
}
};
}
}

通過分析上述代碼,我們不難發現,所有參數化類型 Collection, Iterator 和 LinkedList 中的類型參數 "A" 全都被擦除了。另外,剩下的類型變量 "A" 都用其上限進行了替換,這裏的上限是 Object,見黑體字標出的部分,這是轉化的關鍵部分。

下面我們分析一下在介紹有關泛化方法(generic method)和受限類型參數(bounded type parameter)時舉的那個例子,該段 GJ 代碼經過轉換後的等價 Java 程序如下所示:

interface Comparable
{
public int compareTo(Object that);
}
class Byte implements Comparable
{
private byte value;
public Byte(byte value)
{this.value = value;}
public byte byteValue(){return value;}
public int compareTo(Byte that)
{ return this.value - that.value; }
public int compareTo(Object that)
{ return this.compareTo((Byte)that); }
}
class Collections
{
public static Comparable max(Collection xs)
{
Iterator xi = xs.iterator();
Comparable w = (Comparable)xi.next();
while (xi.hasNext())
{
Comparable x = (Comparable)xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}

同樣請讀者注意黑體字標出的部分,這些關鍵點我們在前面已經介紹過了,故不贅述。唯一需要注意的一點就是第 18,20,22 行出現的Comparable。在泛型轉化中,類型變量應該用其上限來替換,一般情況下這些上限是 "Object",但是當遇到受限的類型參數時,這個上限就不再是 "Object" 了,編譯器會用限制這些類型參數的類型來替換它,上述代碼就用了對 A 進行限制的類型 "Comparable" 來替換 A。

橋方法的引入,爲解決覆蓋問題帶來了方便,但是這種方法還存在一些問題,例如下面這段代碼:

interface Iterator<A>
{
public boolean hasNext ();
public A next ();
}
class Interval implements Iterator<Integer>
{
private int i;
private int n;
public Interval (int l, int u)
{ i = l; n = u; }
public boolean hasNext ()
{ return (i <= n); }
public Integer next ()
{ return new Integer(i++); }
}

根據以上所講的內容,這部分代碼轉換後的 Java 程序應該是如下這個樣子:

interface Iterator {
public boolean hasNext ();
public Object next ();
}
class Interval implements Iterator
{
private int i;
private int n;
public Interval (int l, int u)
{ i = l; n = u; }
public boolean hasNext ()
{ return (i <= n); }
public Integer next%1% ()
{ return new Integer(i++); }
// bridge
public Object next%2%() { return next%1%(); }
}

相信有些讀者已經發現了這裏的問題,這不是一段合法的 Java 源程序,因爲第 14 行和第 16 行的兩個 next() 有相同的參數,無法加以區分。代碼中的 %1% 和 %2% 是爲了區分而人爲加入的,並非 GJ 轉化的結果。

不過,這並不是什麼太大的問題,因爲 Java 虛擬機可以區分這兩個 next() 方法,也就是說,從 Java 源程序的角度來看,上述程序是不正確的,但是當編譯成字節碼時,JVM 可以對兩個 next() 方法進行識別。這是因爲,在 JVM 中,方法定義時所使用的方法簽名包括方法的返回類型,這樣一來,只要 GJ 編譯出的字節碼符合Java字節碼的規範即可,這也正好說明了 GJ 和 JVM 中字節碼規範要求的一致性!

最後,值得一提的是,JDK 5.0 除了在編譯器層面對 Java 中的泛型進行了支持,Java 的類庫爲支持泛型也做了相應地調整,例如,集合框架中所有的標準集合接口都進行了泛型化,同時,集合接口的實現也都進行了相應地泛型化。

Java 中的泛型與 C++ 模板的比較

GJ 程序的語法在表面上與 C++ 中的模板非常類似,但是二者之間有着本質的區別。

首先,Java 語言中的泛型不能接受基本類型作爲類型參數――它只能接受引用類型。這意味着可以定義 List<Integer>,但是不可以定義 List<int>。

其次,在 C++ 模板中,編譯器使用提供的類型參數來擴充模板,因此,爲 List<A> 生成的 C++ 代碼不同於爲 List<B> 生成的代碼,List<A> 和 List<B> 實際上是兩個不同的類。而 Java 中的泛型則以不同的方式實現,編譯器僅僅對這些類型參數進行擦除和替換。類型 ArrayList<Integer> 和 ArrayList<String> 的對象共享相同的類,並且只存在一個 ArrayList 類。

總結

本文通過一些示例從基本原理,重要概念,關鍵技術,以及相似技術比較等多個角度對 Java 語言中的泛型技術進行了介紹,希望這種介紹方法能夠幫助讀者更好地理解和使用泛型。本文主要針對廣大的 Java 語言使用者,在介紹了泛型的基本概念後,重點介紹了比較底層的泛型轉化技術,旨在幫助讀者更加深刻地掌握泛型,筆者相信這部分內容可以使讀者避免對泛型理解的表面化,也所謂知其然更知其所以然。

 

 

 

原文鏈接:http://www.ibm.com/developerworks/cn/java/j-lo-gj/index.html?S_TACT=105AGX52&S_CMP=techcsdn

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章