国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
多角度看 Java 中的泛型

2006 年 12 月 28 日

泛型是 Sun 公司發(fā)布的 JDK 5.0 中的一個(gè)重要特性,它的最大優(yōu)點(diǎn)是提供了程序的類型安全同可以向后兼容。為了幫助讀者更好地理解和使用泛型,本文通過一些示例從基本原理,重要概念,關(guān)鍵技術(shù),以及相似技術(shù)比較等多個(gè)角度對(duì) Java 語(yǔ)言中的泛型技術(shù)進(jìn)行了介紹,重點(diǎn)強(qiáng)調(diào)了泛型中的一些基本但又不是很好理解的概念。

為了避免和 C++ 中的模板混淆,本文簡(jiǎn)要介紹了 Java 中的泛型和 C++ 中的模板的主要區(qū)別,希望這種比較能夠幫助讀者加深對(duì)泛型的理解。

引言

很多 Java 程序員都使用過集合(Collection),集合中元素的類型是多種多樣的,例如,有些集合中的元素是 Byte 類型的,而有些則可能是 String 類型的,等等。Java 語(yǔ)言之所以支持這么多種類的集合,是因?yàn)樗试S程序員構(gòu)建一個(gè)元素類型為 Object 的 Collection,所以其中的元素可以是任何類型。

當(dāng)使用 Collection 時(shí),我們經(jīng)常要做的一件事情就是要進(jìn)行類型轉(zhuǎn)換,當(dāng)轉(zhuǎn)換成所需的類型以后,再對(duì)它們進(jìn)行處理。很明顯,這種設(shè)計(jì)給編程人員帶來了極大的不便,同時(shí)也容易引入錯(cuò)誤。

在很多 Java 應(yīng)用中,上述情況非常普遍,為了解決這個(gè)問題,使 Java 語(yǔ)言變得更加安全好用,近些年的一些編譯器對(duì) Java 語(yǔ)言進(jìn)行了擴(kuò)充,使 Java 語(yǔ)言支持了"泛型",特別是 Sun 公司發(fā)布的 JDK 5.0 更是將泛型作為其中一個(gè)重要的特性加以推廣。

本文首先對(duì)泛型的基本概念和特點(diǎn)進(jìn)行簡(jiǎn)單介紹,然后通過引入幾個(gè)實(shí)例來討論帶有泛型的類,泛型中的子類型,以及范化方法和受限類型參數(shù)等重要概念。為了幫助讀者更加深刻的理解并使用泛型,本文還介紹了泛型的轉(zhuǎn)化,即,如何將帶有泛型的 Java 程序轉(zhuǎn)化成一般的沒有泛型的 Java 程序。這樣,讀者對(duì)泛型的理解就不會(huì)僅僅局限在表面上了??紤]到多數(shù)讀者僅僅是使用泛型,因此本文并未介紹泛型在編譯器中的具體實(shí)現(xiàn)。Java 中的泛型和 C++ 中的模板表面上非常相似,但實(shí)際上二者還是有很大區(qū)別的,本文最后簡(jiǎn)單介紹了 Java 中的泛型與 C++ 模板的主要區(qū)別。





回頁(yè)首


泛型概覽

泛型本質(zhì)上是提供類型的"類型參數(shù)",它們也被稱為參數(shù)化類型(parameterized type)或參量多態(tài)(parametric polymorphism)。其實(shí)泛型思想并不是 Java 最先引入的,C++ 中的模板就是一個(gè)運(yùn)用泛型的例子。

GJ(Generic Java)是對(duì) Java 語(yǔ)言的一種擴(kuò)展,是一種帶有參數(shù)化類型的 Java 語(yǔ)言。用 GJ 編寫的程序看起來和普通的 Java 程序基本相同,只不過多了一些參數(shù)化的類型同時(shí)少了一些類型轉(zhuǎn)換。實(shí)際上,這些 GJ 程序也是首先被轉(zhuǎn)化成一般的不帶泛型的 Java 程序后再進(jìn)行處理的,編譯器自動(dòng)完成了從 Generic Java 到普通 Java 的翻譯。具體的轉(zhuǎn)化過程大致分為以下幾個(gè)部分:

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

轉(zhuǎn)化后的程序和沒有引入泛型時(shí)程序員不得不手工完成轉(zhuǎn)換的程序是非常一致的,具體的轉(zhuǎn)化過程會(huì)在后面介紹。GJ 保持了和 Java 語(yǔ)言以及 Java 虛擬機(jī)很好的兼容性,下面對(duì) GJ 的特點(diǎn)做一個(gè)簡(jiǎn)要的總結(jié)。

  • 類型安全。 泛型的一個(gè)主要目標(biāo)就是提高 Java 程序的類型安全。使用泛型可以使編譯器知道變量的類型限制,進(jìn)而可以在更高程度上驗(yàn)證類型假設(shè)。如果沒有泛型,那么類型的安全性主要由程序員來把握,這顯然不如帶有泛型的程序安全性高。
  • 消除強(qiáng)制類型轉(zhuǎn)換。泛型可以消除源代碼中的許多強(qiáng)制類型轉(zhuǎn)換,這樣可以使代碼更加可讀,并減少出錯(cuò)的機(jī)會(huì)。
  • 向后兼容。支持泛型的 Java 編譯器(例如 JDK5.0 中的 Javac)可以用來編譯經(jīng)過泛型擴(kuò)充的 Java 程序(GJ 程序),但是現(xiàn)有的沒有使用泛型擴(kuò)充的 Java 程序仍然可以用這些編譯器來編譯。
  • 層次清晰,恪守規(guī)范。無論被編譯的源程序是否使用泛型擴(kuò)充,編譯生成的字節(jié)碼均可被虛擬機(jī)接受并執(zhí)行。也就是說不管編譯器的輸入是 GJ 程序,還是一般的 Java 程序,經(jīng)過編譯后的字節(jié)碼都嚴(yán)格遵循《Java 虛擬機(jī)規(guī)范》中對(duì)字節(jié)碼的要求??梢姡盒椭饕窃诰幾g器層面實(shí)現(xiàn)的,它對(duì)于 Java 虛擬機(jī)是透明的。
  • 性能收益。目前來講,用 GJ 編寫的代碼和一般的 Java 代碼在效率上是非常接近的。 但是由于泛型會(huì)給 Java 編譯器和虛擬機(jī)帶來更多的類型信息,因此利用這些信息對(duì) Java 程序做進(jìn)一步優(yōu)化將成為可能。

以上是泛型的一些主要特點(diǎn),下面通過幾個(gè)相關(guān)的例子來對(duì) Java 語(yǔ)言中的泛型進(jìn)行說明。





回頁(yè)首


帶有泛型的類

為了幫助大家更好地理解 Java 語(yǔ)言中的泛型,我們?cè)谶@里先來對(duì)比兩段實(shí)現(xiàn)相同功能的 GJ 代碼和 Java 代碼。通過觀察它們的不同點(diǎn)來對(duì) Java 中的泛型有個(gè)總體的把握,首先來分析一下不帶泛型的 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 提供了兩個(gè)方法,即添加元素的方法 add(Object x),見第 2 行,以及返回該 CollectionIterator 實(shí)例的方法 iterator(),見第 3 行。Iterator 接口也提供了兩個(gè)方法,其一就是判斷是否有下一個(gè)元素的方法 hasNext(),見第 8 行,另外就是返回下一個(gè)元素的方法 next(),見第 7 行。LinkedList 類是對(duì)接口 Collection 的實(shí)現(xiàn),它是一個(gè)含有一系列節(jié)點(diǎn)的鏈表,節(jié)點(diǎn)中的數(shù)據(jù)類型是 Object,這樣就可以創(chuàng)建任意類型的節(jié)點(diǎn)了,比如 Byte, String 等等。上面這段程序就是用沒有泛型的傳統(tǒng)的 Java 語(yǔ)言編寫的代碼。接下來我們分析一下傳統(tǒng)的 Java 語(yǔ)言是如何使用這個(gè)類的。

代碼如下:


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	}                        

從上面的程序我們可以看出,當(dāng)從一個(gè)鏈表中提取元素時(shí)需要進(jìn)行類型轉(zhuǎn)換,這些都要由程序員顯式地完成。如果我們不小心從 String 類型的鏈表中試圖提取一個(gè) Byte 型的元素,見第 15 到第 16 行的代碼,那么這將會(huì)拋出一個(gè)運(yùn)行時(shí)的異常。請(qǐng)注意,上面這段程序可以順利地經(jīng)過編譯,不會(huì)產(chǎn)生任何編譯時(shí)的錯(cuò)誤,因?yàn)榫幾g器并不做類型檢查,這種檢查是在運(yùn)行時(shí)進(jìn)行的。不難發(fā)現(xiàn),傳統(tǒng) Java 語(yǔ)言的這一缺陷推遲了發(fā)現(xiàn)程序中錯(cuò)誤的時(shí)間,從軟件工程的角度來看,這對(duì)軟件的開發(fā)是非常不利的。接下來,我們討論一下如何用 GJ 來實(shí)現(xiàn)同樣功能的程序。源程序如下:


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	}                        

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

下面再來分析一下在 GJ 中是如何對(duì)這個(gè)類進(jìn)行操作的,程序如下:


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	}                        

在這里我們可以看到,有了泛型以后,程序員并不需要進(jìn)行顯式的類型轉(zhuǎn)換,只要賦予一個(gè)參數(shù)化的類型即可,見第 4,8 和 12 行,這是非常方便的,同時(shí)也不會(huì)因?yàn)橥涍M(jìn)行類型轉(zhuǎn)換而產(chǎn)生錯(cuò)誤。另外需要注意的就是當(dāng)試圖從一個(gè)字符串類型的鏈表里提取出一個(gè)元素,然后將它賦值給一個(gè) Byte 型的變量時(shí),見第 16 行,編譯器將會(huì)在編譯時(shí)報(bào)出錯(cuò)誤,而不是由虛擬機(jī)在運(yùn)行時(shí)報(bào)錯(cuò),這是因?yàn)榫幾g器會(huì)在編譯時(shí)刻對(duì) GJ 代碼進(jìn)行類型檢查,此種機(jī)制有利于盡早地發(fā)現(xiàn)并改正錯(cuò)誤。

類型參數(shù)的作用域是定義這個(gè)類型參數(shù)的整個(gè)類,但是不包括靜態(tài)成員函數(shù)。這是因?yàn)楫?dāng)訪問同一個(gè)靜態(tài)成員函數(shù)時(shí),同一個(gè)類的不同實(shí)例可能有不同的類型參數(shù),所以上述提到的那個(gè)作用域不應(yīng)該包括這些靜態(tài)函數(shù),否則就會(huì)引起混亂。





回頁(yè)首


泛型中的子類型

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

上述情形恐怕已經(jīng)深深地印在了廣大讀者的腦中,對(duì)于泛型來講,上述情形有所變化,因此請(qǐng)廣大讀者務(wù)必引起注意。為了說明這種不同,我們還是先來分析一個(gè)小例子,代碼如下所示:


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>,按照以往的經(jīng)驗(yàn),這種賦值好像是正確的,因?yàn)?List<String> 應(yīng)該是 List<Object> 的子類型。這里需要特別注意的是,這種賦值在泛型當(dāng)中是不允許的!List<String> 也不是 List<Object> 的子類型。

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

一般情況下,如果 A 是 B 的子類型,C 是某個(gè)泛型的聲明,那么 C<A> 并不是 C<B> 的子類型,我們也不能將 C<A> 類型的變量賦值給 C<B> 類型的變量。這一點(diǎn)和我們以前接觸的父子類型關(guān)系有很大的出入,因此請(qǐng)讀者務(wù)必引起注意。





回頁(yè)首


泛化方法和受限類型參數(shù)

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


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	}                        

這里定義了一個(gè)接口 Comparable<A>,用來和 A 類型的對(duì)象進(jìn)行比較。類 Byte 實(shí)現(xiàn)了這個(gè)接口,并以它自己作為類型參數(shù),因此,它們自己就可以和自己進(jìn)行比較了。

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

之所以說它是泛化了的方法,是因?yàn)檫@個(gè)方法可以應(yīng)用到很多種類型上。當(dāng)要將一個(gè)方法聲明為泛化方法時(shí),我們只需要在這個(gè)方法的返回類型(A)之前加上一個(gè)類型參數(shù)(A),并用尖括號(hào)(< >)將它括起來。這里的類型參數(shù)(A)是在方法被調(diào)用時(shí)自動(dòng)實(shí)例化的。例如,假設(shè)對(duì)象 m 的類型是 Collection<Byte>,那么當(dāng)使用下面的語(yǔ)句:


Byte x = Collections.max(m);                        

調(diào)用方法 max 時(shí),該方法的參數(shù) A 將被推測(cè)為 Byte。

根據(jù)上面討論的內(nèi)容,泛化方法 max 的完整聲明應(yīng)該是下面的形式:


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

但是,我們見到的 max 在 < A > 中還多了 "implements Comparable<A>" 一項(xiàng),這是什么呢?這就是我們下面將要談到的"受限的類型參數(shù)"。在上面的例子中,類型參數(shù) A 就是一個(gè)受限的的類型參數(shù),因?yàn)樗皇欠褐溉魏晤愋?,而是指那些自己和自己作比較的類型。例如參數(shù)可以被實(shí)例化為 Byte,因?yàn)槌绦蛑杏?Byte implements Comparable<Byte> 的語(yǔ)句,參見第 5 行。這種限制(或者說是范圍)通過如下的方式表示,"類型參數(shù) implements 接口",或是 "類型參數(shù) extend 類",上面程序中的"Byte implements Comparable<Byte>"就是一例。





回頁(yè)首


泛型的轉(zhuǎn)化

在前面的幾部分內(nèi)容當(dāng)中,我們介紹了有關(guān)泛型的基礎(chǔ)知識(shí),到此讀者對(duì) Java 中的泛型技術(shù)應(yīng)該有了一定的了解,接下來的這部分內(nèi)容將討論有關(guān)泛型的轉(zhuǎn)化,即如何將帶有泛型的 Java 代碼轉(zhuǎn)化成一般的沒有泛型 Java 代碼。其實(shí)在前面的部分里,我們或多或少地也提到了一些相關(guān)的內(nèi)容,下面再來詳細(xì)地介紹一下。

首先需要明確的一點(diǎn)是上面所講的這種轉(zhuǎn)化過程是由編譯器(例如:Javac)完成的,虛擬機(jī)并不負(fù)責(zé)完成這一任務(wù)。當(dāng)編譯器對(duì)帶有泛型的 Java 代碼進(jìn)行編譯時(shí),它會(huì)去執(zhí)行類型檢查和類型推斷,然后生成普通的不帶泛型的字節(jié)碼,這種字節(jié)碼可以被一般的Java虛擬機(jī)接收并執(zhí)行,這種技術(shù)被稱為擦除(erasure)。

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

對(duì)于不同的情況,擦除技術(shù)所執(zhí)行的"擦除"動(dòng)作是不同的,主要分為以下幾種情況:

  • 對(duì)于參數(shù)化類型,需要?jiǎng)h除其中的類型參數(shù),例如,LinkedList<A> 將被"擦除"為 LinkedList;
  • 對(duì)于非參數(shù)化類型,不作擦除,或者說用它自己來擦除自己,例如 String 將被"擦除"為 String;
  • 對(duì)于類型變量(有關(guān)類型變量的說明請(qǐng)參考"泛型概覽"相關(guān)內(nèi)容),要用它們的上限來對(duì)它們進(jìn)行替換。多數(shù)情況下這些上限是 Object,但是也有例外,后面的部分將會(huì)對(duì)此進(jìn)行介紹。

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

情況 1. 方法的返回類型是類型參數(shù);

情況 2. 在訪問數(shù)據(jù)域時(shí),域的類型是一個(gè)類型參數(shù)。

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


8			LinkedList<String> ys = new LinkedList<String>();                        9			ys.add("zero"); ys.add("one");                        10			String y = ys.iterator().next();                        這部分代碼轉(zhuǎn)換后就變成了如下的代碼:                        8	       	LinkedList ys = new LinkedList();                        9           ys.add("zero"); ys.add("one");                        10	String y = (String)ys.iterator().next();                        

第 10 行的代碼進(jìn)行了類型轉(zhuǎn)換,這是因?yàn)樵谡{(diào)用 next() 方法時(shí),編譯器發(fā)現(xiàn)該方法的返回值類型是類型參數(shù) A(請(qǐng)參見對(duì)方法 next() 的定義),因此根據(jù)上面提到的情況 1,需要進(jìn)行類型轉(zhuǎn)換。

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

Java 是一種面向?qū)ο蟮恼Z(yǔ)言,因此覆蓋(overridden)是其中的一項(xiàng)重要技術(shù)。覆蓋能夠正常"工作"的前提是方法名和方法的參數(shù)類型及個(gè)數(shù)完全匹配(參數(shù)的順序也應(yīng)一致),為了滿足這項(xiàng)要求,編譯器在泛型轉(zhuǎn)化中引入了橋方法(bridge method)。接下來,我們通過一個(gè)例子來分析一下橋方法在泛型轉(zhuǎn)化中所起的作用。在本文"泛化方法和受限類型參數(shù)"一小節(jié)所給出的代碼中,第 9 行到第 11 行的程序如下所示:


    9		public int compareTo(Byte that) {                        10			return this.value - that.value;                        11		}                        這部分代碼經(jīng)過轉(zhuǎn)化,就變成了下面的樣子:                        9		public int compareTo(Byte that) {                        10			return this.value - that.value;                        11		}                        12  public int compareTo(Object that){                        13			return this.compareTo((Byte)that);                        14		}                        

第 12 行的方法 compareTo(Object that) 就是一個(gè)橋方法,在這里引入這個(gè)方法是為了保證覆蓋能夠正常的發(fā)生。我們?cè)谇懊嫣岬竭^,覆蓋必須保證方法名和參數(shù)的類型及數(shù)目完全匹配,在這里通過引入這個(gè)"橋"即可達(dá)到這一目的,由這個(gè)"橋"進(jìn)行類型轉(zhuǎn)換,并調(diào)用第 9 行參數(shù)類型為 Byte 的方法 compareTo(Byte that),需要注意的一點(diǎn)是這里的 "Object" 也并不一定是完全匹配的類型,但由于它是 Java 語(yǔ)言中類層次結(jié)構(gòu)的根,所以這里用 "Object" 可以接受其他任何類型的參數(shù)。

根據(jù)面向?qū)ο蟮幕靖拍?,我們知道,重載(overloading)允許橋方法和原來的方法共享同一個(gè)方法名,正如上面例子所顯示的那樣,因此橋方法的引入是完全合法的。一般情況下,當(dāng)一個(gè)類實(shí)現(xiàn)了一個(gè)參數(shù)化的接口或是繼承了一個(gè)參數(shù)化的類時(shí),需要引入橋方法。

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

首先來分析一下本文提到的那個(gè) Collection 的例子。這里先是定義了兩個(gè)接口 CollectionIterator,然后又定義了一個(gè)對(duì)接口 Collection 的一個(gè)實(shí)現(xiàn) LinkedList。根據(jù)上面所介紹的對(duì)泛型的轉(zhuǎn)化過程,這段代碼轉(zhuǎn)化后的 Java 程序?yàn)椋?/p>

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) {                        27		       	head = new Node(elt); tail = head;                        28			} else {                        29		       	tail.next = new Node(elt); tail = tail.next;                        30			}                        31		}                        32                        33		public Iterator iterator () {                        34	       	return new Iterator () {                        35		       	protected Node ptr = head;                        36	            public boolean hasNext () { return ptr != null; }                        37		       	public Object next () {                        38			       	if (ptr != null) {                        39				       	Object elt = ptr.elt; ptr = ptr.next; return elt;                        40					} else {                        41				       		throw new NoSuchElementException ();                        42						}                        43				}                        44			};                        45		}                        46	}                        

通過分析上述代碼,我們不難發(fā)現(xiàn),所有參數(shù)化類型 Collection, Iterator 和 LinkedList 中的類型參數(shù) "A" 全都被擦除了。另外,剩下的類型變量 "A" 都用其上限進(jìn)行了替換,這里的上限是 Object,見黑體字標(biāo)出的部分,這是轉(zhuǎn)化的關(guān)鍵部分。

下面我們分析一下在介紹有關(guān)泛化方法(generic method)和受限類型參數(shù)(bounded type parameter)時(shí)舉的那個(gè)例子,該段 GJ 代碼經(jīng)過轉(zhuǎn)換后的等價(jià) Java 程序如下所示:


1	interface Comparable {                        2		public int compareTo(Object that);                        3	}                        4                        5	class Byte implements Comparable {                        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      public int compareTo(Object that){                        13			return this.compareTo((Byte)that);                        14		}                        15	}                        16                        17	class Collections {                        18		public static Comparable max(Collection xs){                        19			Iterator xi = xs.iterator();                        20			Comparable w = (Comparable)xi.next();                        21			while (xi.hasNext()) {                        22				Comparable x = (Comparable)xi.next();                        23				if (w.compareTo(x) < 0) w = x;                        23			}                        24			return w;                        25		}                        26	}                        

同樣請(qǐng)讀者注意黑體字標(biāo)出的部分,這些關(guān)鍵點(diǎn)我們?cè)谇懊嬉呀?jīng)介紹過了,故不贅述。唯一需要注意的一點(diǎn)就是第 18,20,22 行出現(xiàn)的Comparable。在泛型轉(zhuǎn)化中,類型變量應(yīng)該用其上限來替換,一般情況下這些上限是 "Object",但是當(dāng)遇到受限的類型參數(shù)時(shí),這個(gè)上限就不再是 "Object" 了,編譯器會(huì)用限制這些類型參數(shù)的類型來替換它,上述代碼就用了對(duì) A 進(jìn)行限制的類型 "Comparable" 來替換 A。

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


1	interface Iterator<A> {                        2		public boolean hasNext ();                        3		public A next ();                        4	}                        5	class Interval implements Iterator<Integer> {                        6		private int i;                        7		private int n;                        8		public Interval (int l, int u) { i = l; n = u; }                        9		public boolean hasNext () { return (i <= n); }                        10		public Integer next () { return new Integer(i++); }                        11	}                        

根據(jù)以上所講的內(nèi)容,這部分代碼轉(zhuǎn)換后的 Java 程序應(yīng)該是如下這個(gè)樣子:


1	interface Iterator {                        2                        3		public boolean hasNext ();                        4		public Object next ();                        5                        6	}                        7                        8	class Interval implements Iterator {                        9                        10		private int i;                        11		private int n;                        12		public Interval (int l, int u) { i = l; n = u; }                        13		public boolean hasNext () { return (i <= n); }                        14		public Integer next%1% () { return new Integer(i++); }                        15		// bridge                        16		public Object next%2%() { return next%1%(); }                        17                        18	}                        

相信有些讀者已經(jīng)發(fā)現(xiàn)了這里的問題,這不是一段合法的 Java 源程序,因?yàn)榈?14 行和第 16 行的兩個(gè) next() 有相同的參數(shù),無法加以區(qū)分。代碼中的 %1% 和 %2% 是為了區(qū)分而人為加入的,并非 GJ 轉(zhuǎn)化的結(jié)果。

不過,這并不是什么太大的問題,因?yàn)?Java 虛擬機(jī)可以區(qū)分這兩個(gè) next() 方法,也就是說,從 Java 源程序的角度來看,上述程序是不正確的,但是當(dāng)編譯成字節(jié)碼時(shí),JVM 可以對(duì)兩個(gè) next() 方法進(jìn)行識(shí)別。這是因?yàn)椋?JVM 中,方法定義時(shí)所使用的方法簽名包括方法的返回類型,這樣一來,只要 GJ 編譯出的字節(jié)碼符合Java字節(jié)碼的規(guī)范即可,這也正好說明了 GJ 和 JVM 中字節(jié)碼規(guī)范要求的一致性!

最后,值得一提的是,JDK 5.0 除了在編譯器層面對(duì) Java 中的泛型進(jìn)行了支持,Java 的類庫(kù)為支持泛型也做了相應(yīng)地調(diào)整,例如,集合框架中所有的標(biāo)準(zhǔn)集合接口都進(jìn)行了泛型化,同時(shí),集合接口的實(shí)現(xiàn)也都進(jìn)行了相應(yīng)地泛型化。





回頁(yè)首


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

GJ 程序的語(yǔ)法在表面上與 C++ 中的模板非常類似,但是二者之間有著本質(zhì)的區(qū)別。

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

其次,在 C++ 模板中,編譯器使用提供的類型參數(shù)來擴(kuò)充模板,因此,為 List<A> 生成的 C++ 代碼不同于為 List<B> 生成的代碼,List<A> 和 List<B> 實(shí)際上是兩個(gè)不同的類。而 Java 中的泛型則以不同的方式實(shí)現(xiàn),編譯器僅僅對(duì)這些類型參數(shù)進(jìn)行擦除和替換。類型 ArrayList<Integer> 和 ArrayList<String> 的對(duì)象共享相同的類,并且只存在一個(gè) ArrayList 類。





回頁(yè)首


總結(jié)

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



參考資料

  • 診斷 Java 代碼: 輕松掌握 Java 泛型(developerWorks,2003 年 5 月):在本文當(dāng)中,Eric Allen 通過描述諸如基本類型限制和多態(tài)方法等特性來說明泛型的優(yōu)缺點(diǎn)。

  • Java 理論和實(shí)踐: 了解泛型(developerWorks,2005 年 1 月):在本文當(dāng)中,Brian Goetz 通過示例分析了剛剛接觸泛型的新用戶常犯的錯(cuò)誤,從反面給用戶以警示。

  • 介紹 JDK 5.0 中的泛型(developerWorks,2004 年 12 月):一篇關(guān)于 JDK 5.0 中的泛型的教程,該教程解釋了在 Java 語(yǔ)言中引入泛型的動(dòng)機(jī),詳細(xì)介紹了泛型的語(yǔ)法和語(yǔ)義,并講述了如何在自己的類中使用泛型。

  • 馴服 Tiger系列專欄:詳細(xì)地討論有關(guān) Java SE 5 平臺(tái)的改變,并提供一些例子作為快速參考。

  • Java 理論與實(shí)踐系列專欄:探索設(shè)計(jì)模式,可靠軟件設(shè)計(jì)原則,以及應(yīng)用于實(shí)際問題的“最佳實(shí)踐”。

  • GJ: Extending the Java programming language with type parameters:Gilad Bracha, Martin Odersky 等人寫的一篇介紹 GJ 基本概念的文章。

  • Generics in the Java Programming Language:Gilad Bracha 寫的一篇詳細(xì)介紹 JDK 5.0 中的泛型的文章。


關(guān)于作者

 

周晶,2006年4月畢業(yè)于北京航空航天大學(xué)計(jì)算機(jī)學(xué)院,獲計(jì)算機(jī)碩士學(xué)位。主要研究領(lǐng)域?yàn)楦呒?jí)編譯技術(shù),Java虛擬機(jī)技術(shù)。 beyond.zhou@gmail.com


本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
深入理解什么是Java泛型?泛型怎么使用?
Java集合體系結(jié)構(gòu)分析與比較
Java集合總結(jié)
java容器理解
Java中的Set,List,Map的區(qū)別
JAVA提高篇
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服