“如果一個程序只含有數量固定的對象,而且已知它們的存在時間,那麼這個程序可以說是相當簡單的。”
通常,我們的程序需要根據程序運行時才知道的一些標准創建新對象。若非程序正式運行,否則我們根本不知道自己到底需要多少數量的對象,甚至不知道它們的准確類型。為了滿足常規編程的需要,我們要求能在任何時候、任何地點創建任意數量的對象。所以不可依賴一個已命名的句柄來容納自己的每一個對象,就象下面這樣:
MyObject myHandle;
因為根本不知道自己實際需要多少這樣的東西。
為解決這個非常關鍵的問題,Java提供了容納對象(或者對象的句柄)的多種方式。其中內建的類型是數組。此外,Java 的工具(實用程序)庫提供了一些“集合類”(亦稱作“容器類”,但該術語已由AWT使用,所以這裡仍采用“集合”這一稱呼)。利用這些集合類,我們可以容納乃至操縱自己的對象。
我們已知道自己該如何定義及初始化一個數組。數組只是容納對象的一種方式。但由於還有其他大量方法可容納數組,所以是哪些地方使數組顯得如此特別呢?
有兩方面的問題將數組與其他集合類型區分開來:效率和類型。對於Java 來說,為保存和訪問一系列對象(實際是對象的句柄)數組,最有效的方法莫過於數組。數組實際代表一個簡單的線性序列,它使得元素的訪問速度非常快,但我們卻要為這種速度付出代價:創建一個數組對象時,它的大小是固定的,而且不可在那個數組對象的“存在時間”內發生改變。可創建特定大小的一個數組,然後假如用光了存儲空間,就再創建一個新數組,將所有句柄從舊數組移到新數組。這屬於“矢量”(Vector)類的行為。然而,由於為這種大小的靈活性要付出較大的代價,所以我們認為矢量的效率並沒有數組高。
C++的矢量類知道自己容納的是什麼類型的對象,但同 Java 的數組相比,它卻有一個明顯的缺點:C++矢量類的operator[]不能進行范圍檢查,所以很容易超出邊界(然而,它可以查詢 vector 有多大,而且at()方法確實能進行范圍檢查)。在Java 中,無論使用的是數組還是集合,都會進行范圍檢查——若超過邊界,就會獲得一個RuntimeException(運行期違例)錯誤。這類違例指出的是一個程序員錯誤,所以不需要在代碼中檢查它。在另一方面,由於 C++的vector不進行范圍檢查,所以訪問速度較快——在Java 中,由於對數組和集合都要進行范圍檢查,所以對性能有一定的影響。
另外幾種常見的集合類:Vector(矢量)、Stack(堆棧)以及Hashtable(散列表)。這些類都涉及對對象的處理——好象它們沒有特定的類型。換言之,它們將其當作 Object 類型處理(Object類型是Java 中所有類的“根”類)。從某個角度看,這種處理方法是非常合理的:我們僅需構建一個集合,然後任何Java 對象都可以進入那個集合(除基本數據類型外——可用Java 的基本類型封裝類將其作為常數置入集合,或者將其封裝到自己的類內,作為可以變化的值使用)。這再一次反映了數組優於常規集合:創建一個數組時,可令其容納一種特定的類型。這意味著可進行編譯期類型檢查,預防自己設置了錯誤的類型,或者錯誤指定了准備提取的類型。當然,在編譯期或者運行期,Java 會防止我們將不當的消息發給一個對象。
所以我們不必考慮自己的哪種做法更加危險,只要編譯器能及時地指出錯誤,同時在運行期間加快速度,目的也就達到了。此外,用戶很少會對一次違例事件感到非常驚訝的。
考慮到執行效率和類型檢查,應盡可能地采用數組。然而,當我們試圖解決一個更常規的問題時,數組的局限也可能顯得非常明顯。在研究過數組以後,將把重點放到Java 提供的集合類身上。
無論使用的數組屬於什麼類型,數組標識符實際都是指向真實對象的一個句柄。那些對象本身是在內存“堆”裡創建的。堆對象既可“隱式”創建(即默認產生),亦可“顯式”創建(即明確指定,用一個new表達式)。堆對象的一部分(實際是我們能訪問的唯一字段或方法)是只讀的length(長度)成員,它告訴我們那個數組對象裡最多能容納多少元素。對於數組對象,“[]”語法是我們能采用的唯一另類訪問方法。
下面這個例子展示了對數組進行初始化的不同方式,以及如何將數組句柄分配給不同的數組對象。它也揭示出對象數組和基本數據類型數組在使用方法上幾乎是完全一致的。唯一的差別在於對象數組容納的是句柄,而基本數據類型數組容納的是具體的數值:
示例如下:
class Weeble {
} // A small mythical creature
publicclass ArraySize {
publicstaticvoid main(String[] args) {
// Arrays of objects:
Weeble[]a;// Null handle
Weeble[]b =new Weeble[5];// Null handles
Weeble[]c =new Weeble[4];
for (inti = 0; i
c[i] =new Weeble();
Weeble[]d = {new Weeble(),new Weeble(),new Weeble() };
// Compile error:variable a not initialized:
//!System.out.println("a.length=" + a.length);
System.out.println("b.length =" + b.length);
// The handles insidethe array are
// automaticallyinitialized to null:
for (inti = 0; i
System.out.println("b[" + i +"]=" +b[i]);
System.out.println("c.length =" + c.length);
System.out.println("d.length =" + d.length);
a =d;
System.out.println("a.length =" + a.length);
// Java 1.1initialization syntax:
a =new Weeble[] {new Weeble(),new Weeble() };
System.out.println("a.length =" + a.length);
// Arrays ofprimitives:
int[]e;// Null handle
int[]f =newint[5];
int[]g =newint[4];
for (inti = 0; i
g[i] =i *i;
int[]h = { 11, 47, 93 };
// Compile error:variable e not initialized:
//!System.out.println("e.length=" + e.length);
System.out.println("f.length =" + f.length);
// The primitivesinside the array are
// automaticallyinitialized to zero:
for (inti = 0; i
System.out.println("f[" + i +"]=" +f[i]);
System.out.println("g.length =" + g.length);
System.out.println("h.length =" + h.length);
e =h;
System.out.println("e.length =" + e.length);
// Java 1.1initialization syntax:
e =newint[] { 1, 2 };
System.out.println("e.length =" + e.length);
}
} // /:~
輸出如下:
b.length= 5
b[0]=null
b[1]=null
b[2]=null
b[3]=null
b[4]=null
c.length= 4
d.length= 3
a.length= 3
a.length= 2
f.length= 5
f[0]=0
f[1]=0
f[2]=0
f[3]=0
f[4]=0
g.length= 4
h.length= 3
e.length= 3
e.length= 2
其中,數組 a只是初始化成一個 null 句柄。此時,編譯器會禁止我們對這個句柄作任何實際操作,除非已正確地初始化了它。數組 b被初始化成指向由Weeble 句柄構成的一個數組,但那個數組裡實際並未放置任何Weeble對象。然而,我們仍然可以查詢那個數組的大小,因為b指向的是一個合法對象。這也為我們帶來了一個難題:不可知道那個數組裡實際包含了多少個元素,因為 length只告訴我們可將多少元素置入那個數組。換言之,我們只知道數組對象的大小或容量,不知其實際容納了多少個元素。盡管如此,由於數組對象
在創建之初會自動初始化成null,所以可檢查它是否為 null,判斷一個特定的數組“空位”是否容納一個對象。類似地,由基本數據類型構成的數組會自動初始化成零(針對數值類型)、null(字符類型)或者false(布爾類型)。
數組c 顯示出我們首先創建一個數組對象,再將Weeble 對象賦給那個數組的所有“空位”。數組d 揭示出“集合初始化”語法,從而創建數組對象(用 new命令明確進行,類似於數組c),然後用Weeble 對象進行初始化,全部工作在一條語句裡完成。
下面這個表達式:
a = d;
向我們展示了如何取得同一個數組對象連接的句柄,然後將其賦給另一個數組對象,就象我們針對對象句柄的其他任何類型做的那樣。現在,a和d 都指向內存堆內同樣的數組對象。
Java 1.1 加入了一種新的數組初始化語法,可將其想象成“動態集合初始化”。由 d 采用的Java 1.0 集合初始化方法則必須在定義d 的同時進行。但若采用 Java 1.1 的語法,卻可以在任何地方創建和初始化一個數組對象。例如,假設hide()方法用於取得一個Weeble 對象數組,那麼調用它時傳統的方法是:
hide(d);
但在Java 1.1中,亦可動態創建想作為參數傳遞的數組,如下所示:
hide(new Weeble[] {new Weeble(), newWeeble() });
這一新式語法使我們在某些場合下寫代碼更方便了。
對於由基本數據類型構成的數組,它們的運作方式與對象數組極為相似,只是前者直接包容了基本類型的數據值。
集合類只能容納對象句柄。但對一個數組,卻既可令其直接容納基本類型的數據,亦可容納指向對象的句柄。利用象 Integer、Double之類的“封裝器”類,可將基本數據類型的值置入一個集合裡。用於基本數據類型的封裝器類只是在某些場合下才能發揮作用。
無論將基本類型的數據置入數組,還是將其封裝進入位於集合的一個類內,都涉及到執行效率的問題。顯然,若能創建和訪問一個基本數據類型數組,那麼比起訪問一個封裝數據的集合,前者的效率會高出許多。
當然,假如准備一種基本數據類型,同時又想要集合的靈活性(在需要的時候可自動擴展,騰出更多的空間),就不宜使用數組,必須使用由封裝的數據構成的一個集合。大家或許認為針對每種基本數據類型,都應有一種特殊類型的Vector。但Java 並未提供這一特性。某些形式的建模機制或許會在某一天幫助 Java 更好地解決這個問題。這個是 C++比Java 做得好的一個地方,因為C++通過template 關鍵字提供了對“參數化類型”的支持。
假定我們現在想寫一個方法,同時不希望它僅僅返回一樣東西,而是想返回一系列東西。此時,象C 和C++這樣的語言會使問題復雜化,因為我們不能返回一個數組,只能返回指向數組的一個指針。這樣就非常麻煩,因為很難控制數組的“存在時間”,它很容易造成內存“漏洞”的出現。
Java 采用的是類似的方法,但我們能“返回一個數組”。當然,此時返回的實際仍是指向數組的指針。但在Java 裡,我們永遠不必擔心那個數組的是否可用——只要需要,它就會自動存在。而且垃圾收集器會在我們完成後自動將其清除。
請思考如何返回一個字串數組:
publicclass IceCream {
static String[]flav = {"Chocolate","Strawberry","Vanilla Fudge Swirl",
"Mint Chip","Mocha Almond Fudge","Rum Raisin","Praline Cream",
"Mud Pie" };
static String[] flavorSet(intn) {
// Force it to bepositive & within bounds:
n = Math.abs(n) % (flav.length + 1);
String[]results =new String[n];
int[]picks =newint[n];
for (inti = 0; i
picks[i] = -1;
for (inti = 0; i
retry:while (true) {
intt = (int) (Math.random() * flav.length);
for (intj = 0; j
if (picks[j] == t)
continue retry;
picks[i] =t;
results[i] =flav[t];
break;
}
}
returnresults;
}
publicstaticvoid main(String[] args) {
for (inti = 0; i < 20;i++) {
System.out.println("flavorSet(" + i +") = ");
String[]fl =flavorSet(flav.length);
for (intj = 0; j
System.out.println("\t" + fl[j]);
}
}
} // /:~
輸出:
flavorSet(0)=
Chocolate
Vanilla Fudge Swirl
Praline Cream
Mint Chip
Strawberry
Mocha Almond Fudge
Rum Raisin
Mud Pie
flavorSet(1)=
Rum Raisin
Vanilla Fudge Swirl
Mud Pie
Chocolate
Strawberry
Mint Chip
Praline Cream
Mocha Almond Fudge
flavorSet(2)=
Vanilla Fudge Swirl
Rum Raisin
Mocha Almond Fudge
Praline Cream
Mint Chip
Mud Pie
Strawberry
Chocolate
flavorSet(3)=
Mocha Almond Fudge
Praline Cream
Strawberry
Rum Raisin
Mint Chip
Chocolate
Vanilla Fudge Swirl
Mud Pie
flavorSet(4)=
Mocha Almond Fudge
Mint Chip
Vanilla Fudge Swirl
Praline Cream
Strawberry
Rum Raisin
Mud Pie
Chocolate
flavorSet(5)=
Vanilla Fudge Swirl
Mud Pie
Chocolate
Praline Cream
Strawberry
Rum Raisin
Mocha Almond Fudge
Mint Chip
flavorSet(6)=
Rum Raisin
Chocolate
Mud Pie
Praline Cream
Mint Chip
Vanilla Fudge Swirl
Strawberry
Mocha Almond Fudge
flavorSet(7)=
Vanilla Fudge Swirl
Praline Cream
Mocha Almond Fudge
Mint Chip
Chocolate
Strawberry
Mud Pie
Rum Raisin
flavorSet(8)=
Mocha Almond Fudge
Rum Raisin
Mud Pie
Praline Cream
Chocolate
Mint Chip
Strawberry
Vanilla Fudge Swirl
flavorSet(9)=
Mint Chip
Chocolate
Praline Cream
Vanilla Fudge Swirl
Strawberry
Mocha Almond Fudge
Mud Pie
Rum Raisin
flavorSet(10)=
Chocolate
Mud Pie
Strawberry
Mocha Almond Fudge
Rum Raisin
Mint Chip
Praline Cream
Vanilla Fudge Swirl
flavorSet(11)=
Rum Raisin
Chocolate
Vanilla Fudge Swirl
Strawberry
Praline Cream
Mocha Almond Fudge
Mint Chip
Mud Pie
flavorSet(12)=
Chocolate
Mocha Almond Fudge
Mud Pie
Vanilla Fudge Swirl
Strawberry
Rum Raisin
Mint Chip
Praline Cream
flavorSet(13)=
Mocha Almond Fudge
Chocolate
Praline Cream
Strawberry
Mint Chip
Vanilla Fudge Swirl
Rum Raisin
Mud Pie
flavorSet(14)=
Mud Pie
Mint Chip
Praline Cream
Rum Raisin
Chocolate
Vanilla Fudge Swirl
Strawberry
Mocha Almond Fudge
flavorSet(15)=
Chocolate
Vanilla Fudge Swirl
Mocha Almond Fudge
Praline Cream
Strawberry
Rum Raisin
Mint Chip
Mud Pie
flavorSet(16)=
Mint Chip
Mud Pie
Chocolate
Mocha Almond Fudge
Vanilla Fudge Swirl
Praline Cream
Strawberry
Rum Raisin
flavorSet(17)=
Vanilla Fudge Swirl
Strawberry
Mint Chip
Mud Pie
Rum Raisin
Mocha Almond Fudge
Chocolate
Praline Cream
flavorSet(18)=
Chocolate
Mint Chip
Mud Pie
Mocha Almond Fudge
Vanilla Fudge Swirl
Strawberry
Praline Cream
Rum Raisin
flavorSet(19)=
Chocolate
Rum Raisin
Mint Chip
Vanilla Fudge Swirl
Mud Pie
Strawberry
Praline Cream
Mocha Almond Fudge
flavorSet()方法創建了一個名為results的String 數組。該數組的大小為 n——具體數值取決於我們傳遞給方法的自變量。隨後,它從數組 flav 裡隨機挑選一些“香料”(Flavor),並將它們置入 results裡,並最終返回results。返回數組與返回其他任何對象沒什麼區別——最終返回的都是一個句柄。至於數組到底是在flavorSet()裡創建的,還是在其他什麼地方創建的,這個問題並不重要,因為反正返回的僅是一個句柄。一旦我們的操作完成,垃圾收集器會自動關照數組的清除工作。而且只要我們需要數組,它就會乖乖地聽候調遣。
另一方面,注意當flavorSet()隨機挑選香料的時候,它需要保證以前出現過的一次隨機選擇不會再次出現。為達到這個目的,它使用了一個無限while 循環,不斷地作出隨機選擇,直到發現未在picks 數組裡出現過的一個元素為止(當然,也可以進行字串比較,檢查隨機選擇是否在 results數組裡出現過,但字串比較的效率比較低)。若成功,就添加這個元素,並中斷循環(break),再查找下一個(i 值會遞增)。但假若t 是一個已在 picks 裡出現過的數組,就用標簽式的continue 往回跳兩級,強制選擇一個新 t。用一個調試程序可以很清楚地看到這個過程。
main()能顯示出20個完整的香料集合,所以我們看到 flavorSet()每次都用一個隨機順序選擇香料。為體會這一點,最簡單的方法就是將輸出重導向進入一個文件,然後直接觀看這個文件的內容。
為容納一組對象,最適宜的選擇應當是數組。而且假如容納的是一系列基本數據類型,更是必須采用數組。
當我們編寫程序時,通常並不能確切地知道最終需要多少個對象。有些時候甚至想用更復雜的方式來保存對象。為解決這個問題,Java 提供了四種類型的“集合類”:Vector(矢量)、BitSet(位集)、Stack(堆棧)以及Hashtable(散列表)。與擁有集合功能的其他語言相比,盡管這兒的數量顯得相當少,但仍然能用它們解決數量驚人的實際問題。
這些集合類具有形形色色的特征。例如,Stack 實現了一個 LIFO(先入先出)序列,而 Hashtable 是一種“關聯數組”,允許我們將任何對象關聯起來。除此以外,所有Java 集合類都能自動改變自身的大小。所以,我們在編程時可使用數量眾多的對象,同時不必擔心會將集合弄得有多大。
使用Java 集合的“缺點”是在將對象置入一個集合時丟失了類型信息。之所以會發生這種情況,是由於當初編寫集合時,那個集合的程序員根本不知道用戶到底想把什麼類型置入集合。若指示某個集合只允許特定的類型,會妨礙它成為一個“常規用途”的工具,為用戶帶來麻煩。
為解決這個問題,集合實際容納的是類型為Object 的一些對象的句柄。這種類型當然代表Java 中的所有對象,因為它是所有類的根。當然,也要注意這並不包括基本數據類型,因為它們並不是從“任何東西”繼承來的。這是一個很好的方案,只是不適用下述場合:
(1) 將一個對象句柄置入集合時,由於類型信息會被拋棄,所以任何類型的對象都可進入我們的集合——即便特別指示它只能容納特定類型的對象。舉個例子來說,雖然指示它只能容納貓,但事實上任何人都可以把一條狗扔進來。
(2) 由於類型信息不復存在,所以集合能肯定的唯一事情就是自己容納的是指向一個對象的句柄。正式使用它之前,必須對其進行造型,使其具有正確的類型。值得欣慰的是,Java 不允許人們濫用置入集合的對象。假如將一條狗扔進一個貓的集合,那麼仍會將集合內的所有東西都看作貓,所以在使用那條狗時會得到一個“違例”錯誤。在同樣的意義上,假若試圖將一條狗的句柄“造型”到一只貓,那麼運行期間仍會得到一個“違例”錯誤。
下面是個例子:
import java.util.*;
class Cat {
privateintcatNumber;
Cat(inti) {
catNumber =i;
}
void print() {
System.out.println("Cat #" + catNumber);
}
}
class Dog {
privateintdogNumber;
Dog(inti) {
dogNumber =i;
}
void print() {
System.out.println("Dog #" + dogNumber);
}
}
publicclass CatsAndDogs {
publicstaticvoid main(String[] args) {
Vectorcats =newVector();
for (inti = 0; i < 7;i++)
cats.addElement(new Cat(i));
// Not a problem toadd a dog to cats:
cats.addElement(new Dog(7));
for (inti = 0; i
((Cat)cats.elementAt(i)).print();
// Dog is detectedonly at run-time
}
} // /:~
輸出:
Cat#0
Cat#1
Cat#2
Cat#3
Cat#4
Cat#5
Cat#6
Exceptionin thread "main"java.lang.ClassCastException: Dog incompatible with Cat
atjava.lang.ClassCastException.
at CatsAndDogs.main(CatsAndDogs.java:35)
包含一個錯誤。
可以看出,Vector的使用是非常簡單的:先創建一個,再用 addElement()置入對象,以後用 elementAt()取得那些對象(注意Vector 有一個 size()方法,可使我們知道已添加了多少個元素,以便防止誤超邊界,造成違例錯誤)。
Cat和Dog類都非常淺顯——除了都是“對象”之外,它們並無特別之處(倘若不明確指出從什麼類繼承,就默認為從 Object繼承。所以我們不僅能用Vector方法將 Cat對象置入這個集合,也能添加Dog對象,同時不會在編譯期和運行期得到任何出錯提示。用Vector 方法elementAt()獲取原本認為是Cat 的對象時,實際獲得的是指向一個Object 的句柄,必須將那個對象造型為Cat。隨後,需要將整個表達式用括號封閉起來,在為Cat調用print()方法之前進行強制造型;否則就會出現一個語法錯誤。在運行期間,如果試圖將Dog對象造型為 Cat,就會得到一個違例。
這些處理的意義都非常深遠。盡管顯得有些麻煩,但卻獲得了安全上的保證。我們從此再難偶然造成一些隱藏得深的錯誤。若程序的一個部分(或幾個部分)將對象插入一個集合,但我們只是通過一次違例在程序的某個部分發現一個錯誤的對象置入了集合,就必須找出插入錯誤的位置。當然,可通過檢查代碼達到這個目的,但這或許是最笨的調試工具。另一方面,我們可從一些標准化的集合類開始自己的編程。盡管它們在功能上存在一些不足,且顯得有些笨拙,但卻能保證沒有隱藏的錯誤。
在某些情況下,程序似乎正確地工作,不造型回我們原來的類型。第一種情況是相當特殊的:String 類從編譯器獲得了額外的幫助,使其能夠正常工作。只要編譯器期待的是一個String 對象,但它沒有得到一個,就會自動調用在Object 裡定義、並且能夠由任何Java 類覆蓋的toString()方法。這個方法能生成滿足要求的String對象,然後在我們需要的時候使用。
因此,為了讓自己類的對象能顯示出來,要做的全部事情就是覆蓋toString()方法,
如下例所示:
import java.util.*;
class Mouse {
privateintmouseNumber;
Mouse(inti) {
mouseNumber =i;
}
// Magic method:
public String toString() {
return"This is Mouse #" +mouseNumber;
}
void print(Stringmsg) {
if (msg != null)
System.out.println(msg);
System.out.println("Mouse number" + mouseNumber);
}
}
class MouseTrap {
staticvoid caughtYa(Objectm) {
Mousemouse = (Mouse)m;// Cast from Object
mouse.print("Caught one!");
}
}
publicclass WorksAnyway {
publicstaticvoid main(String[] args) {
Vectormice =newVector();
for (inti = 0; i < 3;i++)
mice.addElement(new Mouse(i));
for (inti = 0; i
// No cast necessary,automatic call
// toObject.toString():
System.out.println("Free mouse:" + mice.elementAt(i));
MouseTrap.caughtYa(mice.elementAt(i));
}
}
} // /:~
輸出如下:
Freemouse: This is Mouse #0
Caughtone!
Mousenumber 0
Freemouse: This is Mouse #1
Caughtone!
Mousenumber 1
Freemouse: This is Mouse #2
Caughtone!
Mousenumber 2
可在Mouse 裡看到對toString()的重定義代碼。在main()的第二個for 循環中,可發現下述語句:
System.out.println("Free mouse: "+ mice.elementAt(i));
在“+”後,編譯器預期看到的是一個String 對象。elementAt()生成了一個 Object,所以為獲得希望的String,編譯器會默認調用toString()。但不幸的是,只有針對String 才能得到象這樣的結果;其他任何類型都不會進行這樣的轉換。
隱藏造型的第二種方法已在Mousetrap裡得到了應用。caughtYa()方法接收的不是一個Mouse,而是一個Object。隨後再將其造型為一個Mouse。當然,這樣做是非常冒失的,因為通過接收一個 Object,任何東西都可以傳遞給方法。然而,假若造型不正確——如果我們傳遞了錯誤的類型——就會在運行期間得到一個違例錯誤。這當然沒有在編譯期進行檢查好,但仍然能防止問題的發生。注意在使用這個方法時毋需進行造型:
MouseTrap.caughtYa(mice.elementAt(i));
一個更“健壯”的方案是用Vector創建一個新類,使其只接收我們指定的類型,也只生成我們希望的類型。
如下所示:
import java.util.*;
class Gopher {
privateintgopherNumber;
Gopher(inti) {
gopherNumber =i;
}
void print(Stringmsg) {
if (msg != null)
System.out.println(msg);
System.out.println("Gopher number" + gopherNumber);
}
}
class GopherTrap {
staticvoid caughtYa(Gopherg) {
g.print("Caught one!");
}
}
class GopherVector {
privateVector v =newVector();
publicvoid addElement(Gopherm) {
v.addElement(m);
}
public Gopher elementAt(intindex) {
return (Gopher)v.elementAt(index);
}
publicint size() {
returnv.size();
}
publicstaticvoid main(String[] args) {
GopherVectorgophers =new GopherVector();
for (inti = 0; i < 3;i++)
gophers.addElement(new Gopher(i));
for (inti = 0; i
GopherTrap.caughtYa(gophers.elementAt(i));
}
} // /:~
輸出如下:
Caughtone!
Gophernumber 0
Caughtone!
Gophernumber 1
Caughtone!
Gophernumber 2
GopherVector 類有一個類型為Vector的private成員(從Vector繼承有些麻煩,理由稍後便知),而且方法也和Vector 類似。然而,它不會接收和產生普通Object,只對 Gopher對象感興趣。
由於GopherVector只接收一個 Gopher(地鼠),所以假如我們使用:
gophers.addElement(new Pigeon());
就會在編譯期間獲得一條出錯消息。采用這種方式,盡管從編碼的角度看顯得更令人沉悶,但可以立即判斷出是否使用了正確的類型。
注意在使用elementAt()時不必進行造型——它肯定是一個Gopher。
3. 參數化類型
這類問題並不是孤立的——我們許多時候都要在其他類型的基礎上創建新類型。此時,在編譯期間擁有特定的類型信息是非常有幫助的。這便是“參數化類型”的概念。在C++中,它由語言通過“模板”獲得了直接支持。至少,Java 保留了關鍵字generic,期望有一天能夠支持參數化類型。但我們現在無法確定這一天何時會來臨。