一、 創(chuàng)建支持泛型的類
首先,你必須學(xué)習(xí)如何創(chuàng)建一個(gè)允許存在"泛型類型"的類。這意味著無論何時(shí)實(shí)例化你的類,你都能夠指定一個(gè)或多個(gè)Java類型與該類相關(guān)聯(lián)。為了說明這個(gè)問題,請考慮列表1中的一個(gè)簡單示例類。
注意,列表1中的類是如何聲明的。它在尖括號之間指定三個(gè)泛型。這些泛型是真實(shí)類型的占位符。當(dāng)你聲明一個(gè)這種類型的類時(shí),你可以指定一個(gè)類來代替ONE,TWO和THREE。如果你不這樣做,那么該類將使用Object的默認(rèn)類型。
這個(gè)類顯示出怎樣設(shè)計(jì)一個(gè)類來接收三個(gè)泛型類型。當(dāng)你創(chuàng)建一個(gè)這種類型的類時(shí)你要支持準(zhǔn)確的類型。
列表1.泛型類:
package com.heatonresearch.examples.collections;public class Example { private ONE one; private TWO two; private THREE three; public ONE getOne() { return one; } public void setOne(ONE one) { this.one = one; } public THREE getThree() { return three; } public void setThree(THREE three) { this.three = three; } public TWO getTwo() { return two; } public void setTwo(TWO two) { this.two = two; } public static void main(String args[]) {Example example = new Example();example.setOne(1.5);example.setTwo(2);example.setThree("Three"); }}
下面是如何實(shí)例化一個(gè)Example類型的類的情形:
Example example=new Example();
前面的代碼將代替具體的Double,Integer和String類型-相當(dāng)于在列表1中的"ONE"、"TWO"和"THREE"占位符。你可以看到這些變量都有這些類型,通過下面三行設(shè)置它們的值。
example.setOne(1.5);example.setTwo(2);example.setThree("Three");
現(xiàn)在,既然你已經(jīng)知道如何創(chuàng)建一個(gè)使用泛型的定制類,那么創(chuàng)建一個(gè)使用泛型的定制集合類則更為簡單些。
二、 創(chuàng)建一個(gè)Queue類
一個(gè)隊(duì)列是一個(gè)很有用的數(shù)據(jù)結(jié)構(gòu)。為了理解一個(gè)隊(duì)列的功能,你可以想像在一個(gè)娛樂公園人們排隊(duì)騎馬的情形。人們從隊(duì)的后面進(jìn)入到隊(duì)中。為此,他們等待而最后到達(dá)隊(duì)伍的前端。其順序不能改變。
這種情形可以被應(yīng)用到一個(gè)隊(duì)列類上去。它共有兩個(gè)方法,分別是"push"和"pop"。你使用push方法來把對象放置到隊(duì)列中,而使用pop方法從隊(duì)列中刪除一項(xiàng)。例如,如果你使用push方法把三個(gè)對象添加到隊(duì)列上,那么連續(xù)調(diào)用pop三次將以同樣順序從隊(duì)列中刪除這三個(gè)元素。這正與娛樂公園的情形相一致。如果有三個(gè)人以一特定的順序進(jìn)入隊(duì)中,他們將以相同的順序得到騎馬娛樂。
下列代碼顯示出怎么實(shí)現(xiàn)一個(gè)使用泛型的Java隊(duì)列。
package com.heatonresearch.examples.collections;import java.util.*;public class Queue {private ArrayList list = new ArrayList();public void push(T obj) { list.add(obj); }public T pop() throws QueueException {if (size() == 0)throw new QueueException("Tried to pop something from the queue, " +"when it was empty");T result = list.get(0);list.remove(0);return result;}public boolean isEmpty() { return list.isEmpty(); }public int size() { return list.size(); }public void clear() { list.clear(); }}
前面的代碼聲明了隊(duì)列類,這樣它可以接收一個(gè)泛型類型。
public class Queue
泛型類型"T"是該類類型-它將被放入到該隊(duì)列中去。為了把這些項(xiàng)存儲到一個(gè)隊(duì)列中,該類還要創(chuàng)建一個(gè)接收"T"類型的ArrayList。
push方法很簡單的。它接收單一的類型為泛型"T"的對象,并且把它添加到ArrayList上。
pop方法稍微復(fù)雜些。首先,如果你要從隊(duì)列中彈出一個(gè)對象,并且如果在隊(duì)列中沒有對象,那么該類將拋出一個(gè)QueueException類型的異常。下面是QueueException類。
package com.heatonresearch.examples.collections;public class QueueException extends Exception { public QueueException(String msg) {super(msg); }}
下面是拋出QueueException類型異常的代碼:
if (size() == 0)throw new QueueException("Tried to pop something from the queue, " + "when it was empty");
如果隊(duì)列不空,該方法將從隊(duì)列中檢索最后一個(gè)元素,在一個(gè)名叫result的變量中存儲它,然后從該列表中刪除這個(gè)項(xiàng)。下面幾行代碼實(shí)現(xiàn)了這一功能:
T result = list.get(0);list.remove(0);return result;
注意,該臨時(shí)變量也是泛型類型"T"。當(dāng)這個(gè)類與真實(shí)的代表泛型類型的Java類型一起使用時(shí),為了實(shí)現(xiàn)程度上的兼容性,無論你何時(shí)存取這些變量,確??偸鞘褂梅盒皖愋褪欠浅V匾?。
三、 測試Queue類
下列類用于測試"泛型"隊(duì)列。
package com.heatonresearch.examples.collections; public class TestQueue {public static void main(String args[]) { Queue queue = new Queue(); queue.push(1); queue.push(2); queue.push(3); try {System.out.println("Pop 1:" + queue.pop());System.out.println("Pop 2:" + queue.pop());System.out.println("Pop 3:" + queue.pop()); } catch (QueueException e) { e.printStackTrace(); }} }
前面的代碼中創(chuàng)建的隊(duì)列僅接收整型對象。
Queue queue = new Queue();
接下來的測試把三個(gè)整數(shù)添加到該隊(duì)列上。
queue.push(1);queue.push(2);queue.push(3);
注意,添加到該隊(duì)列中的這些數(shù)字都是原始的類型。因?yàn)镴2SE的自動裝箱特性,這些原始的int類型被自動地轉(zhuǎn)變成Integer對象。
接下來,該測試使用pop方法檢索對象。在該隊(duì)列為空的情況下,該測試捕獲到QueueException異常。從隊(duì)列中彈出三個(gè)數(shù)字的結(jié)果是:
123
盡管在這里作為一接收的整數(shù)隊(duì)列顯示,但是因?yàn)榉盒?,所以?duì)列類對于任何Java對象情況都能正常工作。
四、 創(chuàng)建一個(gè)可預(yù)知的Stack集合
這里是一個(gè)更復(fù)雜的集合類型-它實(shí)現(xiàn)了一個(gè)堆棧以使你在實(shí)際刪除一個(gè)對象之前能夠預(yù)知或"可偷看"。你可以或者通過使用一個(gè)迭代算子或使用J2SE 5.0的新的"for each"結(jié)構(gòu)語句來進(jìn)行預(yù)知。
這個(gè)PeekableStack類是一個(gè)先進(jìn)后出(FILO)棧-讓你遍歷當(dāng)前棧中的內(nèi)容。它的實(shí)現(xiàn)使用了兩個(gè)類。首先,PeekableStack類實(shí)現(xiàn)實(shí)際的棧部分。其次,PeekableStackIterator類實(shí)現(xiàn)一個(gè)"Java標(biāo)準(zhǔn)的"Iterator類-你可以用它來遍歷整個(gè)棧。列表 2(見所附源代碼文件)顯示出PeekableStack類的具體編碼。
注意,列表2中的PeekableStack類實(shí)現(xiàn)了Iterable接口。這對于支持新型的J2SE 5.0"for-each"結(jié)構(gòu)語句是必要的。該Iterable接口用于指定你的集合支持"iterator"方法-它返回一個(gè)迭代算子。如果沒有這個(gè)接口,你的類將無法與新型的"for-each"結(jié)構(gòu)語句相兼容。
這個(gè)可預(yù)知的棧包含push和pop方法,就象隊(duì)列一樣。該push方法僅僅是比隊(duì)列稍微復(fù)雜些。而push方法負(fù)責(zé)把對象添加到棧上去并增加版本數(shù)(version)。
這個(gè)version變量允許PeekableStackIterator類保證沒有修改操作發(fā)生。在迭代算子創(chuàng)建時(shí),這個(gè)算子保留一份當(dāng)前版本數(shù)。如果棧上通過調(diào)用push方法發(fā)生任何變化,那么這個(gè)版本數(shù)就不會匹配;此不匹配將導(dǎo)致算子拋出一個(gè) ConcurrentModificationException異常。
pop方法稍微復(fù)雜些。首先,它必須決定在該列表中的最后一個(gè)元素,這是通過獲得列表的大小并且減去1而得到的。
int last = list.size() - 1;
如果這個(gè)結(jié)果是一個(gè)小于零的數(shù)字,那么該棧就是空的,因此pop方法就返回null。
if (last < 0) return null;
如果在棧中存在最后一個(gè)元素,那么就從列表中檢索它。在從列表中成功地檢索這個(gè)項(xiàng)后,你可以把它刪除。
T result = list.get(last);list.remove(last);
最后,返回從列表中檢索的對象。
return result;
為支持"for each"迭代,PeekableStack類的iterator方法返回一個(gè)"Java標(biāo)準(zhǔn)的"Iterator類-你可以用它來遍歷包含在棧中的所有對象。iterator方法創(chuàng)建一個(gè)新的iterator并且返回之。
PeekableStackIterator peekableStackIterator=newPeekableStackIterator(this, list);
如你所見,該iterator類接收當(dāng)前棧和棧的項(xiàng)目列表作為構(gòu)造器參數(shù)。這些值將為PeekableStackIterator所用-下一節(jié)將討論之。
五、 創(chuàng)建一個(gè)可預(yù)知的Stack迭代算子
如果PeekableStack類將要同Java中新的"for each"結(jié)構(gòu)語句一起使用,那么你必須創(chuàng)建一個(gè)"Java標(biāo)準(zhǔn)的"Iterator。列表3顯示出一個(gè)PeekableStackIterator類的實(shí)現(xiàn)。
在列表3中,迭代子實(shí)際上并沒有以任何方式改變棧的值;代之的是,該迭代子追蹤它在元素列表中的當(dāng)前位置并且總是返回下一個(gè)元素。因?yàn)檫@個(gè)信息被存儲在iteration類本身,所以有可能存在多個(gè)算子運(yùn)行于相同的棧上。
下列程序用于測試可預(yù)知的棧。
package com.heatonresearch.examples.collections; import java.util.*; public class TestPeekableStack {public static void main(String args[]) { PeekableStack stack = new PeekableStack(); stack.push(1); stack.push(2); stack.push(3); for (int i : stack) { System.out.println(i); } System.out.println("Pop 1:" + stack.pop()); System.out.println("Pop 2:" + stack.pop()); System.out.println("Pop 3:" + stack.pop());}}
如你所見,有三個(gè)項(xiàng)被添加到棧上去。然后,這三個(gè)項(xiàng)被使用新的"for each"結(jié)構(gòu)語句顯示出來。
for( int i: stack){ System.out.println( i ); }
因此,你看到怎樣成功地實(shí)現(xiàn)一集合-它支持新型的J2SE慣例-既有泛型也有"for each"結(jié)構(gòu)語句。如你所見,創(chuàng)建與J2SE 5.0中新型的結(jié)構(gòu)相兼容的集合是相當(dāng)容易的-這只需要利用泛型并且實(shí)現(xiàn)恰當(dāng)?shù)慕涌诩纯?。你會發(fā)現(xiàn)這樣的集合類被無縫地集成到J2SE 5.0中。
首先,你必須學(xué)習(xí)如何創(chuàng)建一個(gè)允許存在"泛型類型"的類。這意味著無論何時(shí)實(shí)例化你的類,你都能夠指定一個(gè)或多個(gè)Java類型與該類相關(guān)聯(lián)。為了說明這個(gè)問題,請考慮列表1中的一個(gè)簡單示例類。
注意,列表1中的類是如何聲明的。它在尖括號之間指定三個(gè)泛型。這些泛型是真實(shí)類型的占位符。當(dāng)你聲明一個(gè)這種類型的類時(shí),你可以指定一個(gè)類來代替ONE,TWO和THREE。如果你不這樣做,那么該類將使用Object的默認(rèn)類型。
這個(gè)類顯示出怎樣設(shè)計(jì)一個(gè)類來接收三個(gè)泛型類型。當(dāng)你創(chuàng)建一個(gè)這種類型的類時(shí)你要支持準(zhǔn)確的類型。
列表1.泛型類:
package com.heatonresearch.examples.collections;public class Example { private ONE one; private TWO two; private THREE three; public ONE getOne() { return one; } public void setOne(ONE one) { this.one = one; } public THREE getThree() { return three; } public void setThree(THREE three) { this.three = three; } public TWO getTwo() { return two; } public void setTwo(TWO two) { this.two = two; } public static void main(String args[]) {Example example = new Example();example.setOne(1.5);example.setTwo(2);example.setThree("Three"); }}
下面是如何實(shí)例化一個(gè)Example類型的類的情形:
Example example=new Example();
前面的代碼將代替具體的Double,Integer和String類型-相當(dāng)于在列表1中的"ONE"、"TWO"和"THREE"占位符。你可以看到這些變量都有這些類型,通過下面三行設(shè)置它們的值。
example.setOne(1.5);example.setTwo(2);example.setThree("Three");
現(xiàn)在,既然你已經(jīng)知道如何創(chuàng)建一個(gè)使用泛型的定制類,那么創(chuàng)建一個(gè)使用泛型的定制集合類則更為簡單些。
二、 創(chuàng)建一個(gè)Queue類
一個(gè)隊(duì)列是一個(gè)很有用的數(shù)據(jù)結(jié)構(gòu)。為了理解一個(gè)隊(duì)列的功能,你可以想像在一個(gè)娛樂公園人們排隊(duì)騎馬的情形。人們從隊(duì)的后面進(jìn)入到隊(duì)中。為此,他們等待而最后到達(dá)隊(duì)伍的前端。其順序不能改變。
這種情形可以被應(yīng)用到一個(gè)隊(duì)列類上去。它共有兩個(gè)方法,分別是"push"和"pop"。你使用push方法來把對象放置到隊(duì)列中,而使用pop方法從隊(duì)列中刪除一項(xiàng)。例如,如果你使用push方法把三個(gè)對象添加到隊(duì)列上,那么連續(xù)調(diào)用pop三次將以同樣順序從隊(duì)列中刪除這三個(gè)元素。這正與娛樂公園的情形相一致。如果有三個(gè)人以一特定的順序進(jìn)入隊(duì)中,他們將以相同的順序得到騎馬娛樂。
下列代碼顯示出怎么實(shí)現(xiàn)一個(gè)使用泛型的Java隊(duì)列。
package com.heatonresearch.examples.collections;import java.util.*;public class Queue {private ArrayList list = new ArrayList();public void push(T obj) { list.add(obj); }public T pop() throws QueueException {if (size() == 0)throw new QueueException("Tried to pop something from the queue, " +"when it was empty");T result = list.get(0);list.remove(0);return result;}public boolean isEmpty() { return list.isEmpty(); }public int size() { return list.size(); }public void clear() { list.clear(); }}
前面的代碼聲明了隊(duì)列類,這樣它可以接收一個(gè)泛型類型。
public class Queue
泛型類型"T"是該類類型-它將被放入到該隊(duì)列中去。為了把這些項(xiàng)存儲到一個(gè)隊(duì)列中,該類還要創(chuàng)建一個(gè)接收"T"類型的ArrayList。
push方法很簡單的。它接收單一的類型為泛型"T"的對象,并且把它添加到ArrayList上。
pop方法稍微復(fù)雜些。首先,如果你要從隊(duì)列中彈出一個(gè)對象,并且如果在隊(duì)列中沒有對象,那么該類將拋出一個(gè)QueueException類型的異常。下面是QueueException類。
package com.heatonresearch.examples.collections;public class QueueException extends Exception { public QueueException(String msg) {super(msg); }}
下面是拋出QueueException類型異常的代碼:
if (size() == 0)throw new QueueException("Tried to pop something from the queue, " + "when it was empty");
如果隊(duì)列不空,該方法將從隊(duì)列中檢索最后一個(gè)元素,在一個(gè)名叫result的變量中存儲它,然后從該列表中刪除這個(gè)項(xiàng)。下面幾行代碼實(shí)現(xiàn)了這一功能:
T result = list.get(0);list.remove(0);return result;
注意,該臨時(shí)變量也是泛型類型"T"。當(dāng)這個(gè)類與真實(shí)的代表泛型類型的Java類型一起使用時(shí),為了實(shí)現(xiàn)程度上的兼容性,無論你何時(shí)存取這些變量,確??偸鞘褂梅盒皖愋褪欠浅V匾?。
三、 測試Queue類
下列類用于測試"泛型"隊(duì)列。
package com.heatonresearch.examples.collections; public class TestQueue {public static void main(String args[]) { Queue queue = new Queue(); queue.push(1); queue.push(2); queue.push(3); try {System.out.println("Pop 1:" + queue.pop());System.out.println("Pop 2:" + queue.pop());System.out.println("Pop 3:" + queue.pop()); } catch (QueueException e) { e.printStackTrace(); }} }
前面的代碼中創(chuàng)建的隊(duì)列僅接收整型對象。
Queue queue = new Queue();
接下來的測試把三個(gè)整數(shù)添加到該隊(duì)列上。
queue.push(1);queue.push(2);queue.push(3);
注意,添加到該隊(duì)列中的這些數(shù)字都是原始的類型。因?yàn)镴2SE的自動裝箱特性,這些原始的int類型被自動地轉(zhuǎn)變成Integer對象。
接下來,該測試使用pop方法檢索對象。在該隊(duì)列為空的情況下,該測試捕獲到QueueException異常。從隊(duì)列中彈出三個(gè)數(shù)字的結(jié)果是:
123
盡管在這里作為一接收的整數(shù)隊(duì)列顯示,但是因?yàn)榉盒?,所以?duì)列類對于任何Java對象情況都能正常工作。
四、 創(chuàng)建一個(gè)可預(yù)知的Stack集合
這里是一個(gè)更復(fù)雜的集合類型-它實(shí)現(xiàn)了一個(gè)堆棧以使你在實(shí)際刪除一個(gè)對象之前能夠預(yù)知或"可偷看"。你可以或者通過使用一個(gè)迭代算子或使用J2SE 5.0的新的"for each"結(jié)構(gòu)語句來進(jìn)行預(yù)知。
這個(gè)PeekableStack類是一個(gè)先進(jìn)后出(FILO)棧-讓你遍歷當(dāng)前棧中的內(nèi)容。它的實(shí)現(xiàn)使用了兩個(gè)類。首先,PeekableStack類實(shí)現(xiàn)實(shí)際的棧部分。其次,PeekableStackIterator類實(shí)現(xiàn)一個(gè)"Java標(biāo)準(zhǔn)的"Iterator類-你可以用它來遍歷整個(gè)棧。列表 2(見所附源代碼文件)顯示出PeekableStack類的具體編碼。
注意,列表2中的PeekableStack類實(shí)現(xiàn)了Iterable接口。這對于支持新型的J2SE 5.0"for-each"結(jié)構(gòu)語句是必要的。該Iterable接口用于指定你的集合支持"iterator"方法-它返回一個(gè)迭代算子。如果沒有這個(gè)接口,你的類將無法與新型的"for-each"結(jié)構(gòu)語句相兼容。
這個(gè)可預(yù)知的棧包含push和pop方法,就象隊(duì)列一樣。該push方法僅僅是比隊(duì)列稍微復(fù)雜些。而push方法負(fù)責(zé)把對象添加到棧上去并增加版本數(shù)(version)。
這個(gè)version變量允許PeekableStackIterator類保證沒有修改操作發(fā)生。在迭代算子創(chuàng)建時(shí),這個(gè)算子保留一份當(dāng)前版本數(shù)。如果棧上通過調(diào)用push方法發(fā)生任何變化,那么這個(gè)版本數(shù)就不會匹配;此不匹配將導(dǎo)致算子拋出一個(gè) ConcurrentModificationException異常。
pop方法稍微復(fù)雜些。首先,它必須決定在該列表中的最后一個(gè)元素,這是通過獲得列表的大小并且減去1而得到的。
int last = list.size() - 1;
如果這個(gè)結(jié)果是一個(gè)小于零的數(shù)字,那么該棧就是空的,因此pop方法就返回null。
if (last < 0) return null;
如果在棧中存在最后一個(gè)元素,那么就從列表中檢索它。在從列表中成功地檢索這個(gè)項(xiàng)后,你可以把它刪除。
T result = list.get(last);list.remove(last);
最后,返回從列表中檢索的對象。
return result;
為支持"for each"迭代,PeekableStack類的iterator方法返回一個(gè)"Java標(biāo)準(zhǔn)的"Iterator類-你可以用它來遍歷包含在棧中的所有對象。iterator方法創(chuàng)建一個(gè)新的iterator并且返回之。
PeekableStackIterator peekableStackIterator=newPeekableStackIterator(this, list);
如你所見,該iterator類接收當(dāng)前棧和棧的項(xiàng)目列表作為構(gòu)造器參數(shù)。這些值將為PeekableStackIterator所用-下一節(jié)將討論之。
五、 創(chuàng)建一個(gè)可預(yù)知的Stack迭代算子
如果PeekableStack類將要同Java中新的"for each"結(jié)構(gòu)語句一起使用,那么你必須創(chuàng)建一個(gè)"Java標(biāo)準(zhǔn)的"Iterator。列表3顯示出一個(gè)PeekableStackIterator類的實(shí)現(xiàn)。
在列表3中,迭代子實(shí)際上并沒有以任何方式改變棧的值;代之的是,該迭代子追蹤它在元素列表中的當(dāng)前位置并且總是返回下一個(gè)元素。因?yàn)檫@個(gè)信息被存儲在iteration類本身,所以有可能存在多個(gè)算子運(yùn)行于相同的棧上。
下列程序用于測試可預(yù)知的棧。
package com.heatonresearch.examples.collections; import java.util.*; public class TestPeekableStack {public static void main(String args[]) { PeekableStack stack = new PeekableStack(); stack.push(1); stack.push(2); stack.push(3); for (int i : stack) { System.out.println(i); } System.out.println("Pop 1:" + stack.pop()); System.out.println("Pop 2:" + stack.pop()); System.out.println("Pop 3:" + stack.pop());}}
如你所見,有三個(gè)項(xiàng)被添加到棧上去。然后,這三個(gè)項(xiàng)被使用新的"for each"結(jié)構(gòu)語句顯示出來。
for( int i: stack){ System.out.println( i ); }
因此,你看到怎樣成功地實(shí)現(xiàn)一集合-它支持新型的J2SE慣例-既有泛型也有"for each"結(jié)構(gòu)語句。如你所見,創(chuàng)建與J2SE 5.0中新型的結(jié)構(gòu)相兼容的集合是相當(dāng)容易的-這只需要利用泛型并且實(shí)現(xiàn)恰當(dāng)?shù)慕涌诩纯?。你會發(fā)現(xiàn)這樣的集合類被無縫地集成到J2SE 5.0中。

