無論使用的數組屬於什麼類型,數組標識符實際都是指向真實對象的一個句柄。那些對象本身是在內存“堆”裡創建的。堆對象既可“隱式”創建(即默認產生),亦可“顯式”創建(即明確指定,用一個new表達式)。堆對象的一部分(實際是我們能訪問的唯一字段或方法)是只讀的length(長度)成員,它告訴我們那個數組對象裡最多能容納多少元素。對於數組對象,“[]”語法是我們能采用的唯一另類訪問方法。
下面這個例子展示了對數組進行初始化的不同方式,以及如何將數組句柄分配給不同的數組對象。它也揭示出對象數組和基本數據類型數組在使用方法上幾乎是完全一致的。唯一的差別在於對象數組容納的是句柄,而基本數據類型數組容納的是具體的數值(若在執行此程序時遇到困難,請參考第3章的“賦值”小節):
//: ArraySize.java // Initialization & re-assignment of arrays package c08; class Weeble {} // A small mythical creature public class ArraySize { public static void main(String[] args) { // Arrays of objects: Weeble[] a; // Null handle Weeble[] b = new Weeble[5]; // Null handles Weeble[] c = new Weeble[4]; for(int i = 0; i < c.length; 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 inside the array are // automatically initialized to null: for(int i = 0; i < b.length; 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.1 initialization syntax: a = new Weeble[] { new Weeble(), new Weeble() }; System.out.println("a.length = " + a.length); // Arrays of primitives: int[] e; // Null handle int[] f = new int[5]; int[] g = new int[4]; for(int i = 0; i < g.length; 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 primitives inside the array are // automatically initialized to zero: for(int i = 0; i < f.length; 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.1 initialization syntax: e = new int[] { 1, 2 }; System.out.println("e.length = " + e.length); } } ///:~
Here’s the output from the program:
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(), new Weeble() });
這一新式語法使我們在某些場合下寫代碼更方便了。
上述例子的第二部分揭示出這樣一個問題:對於由基本數據類型構成的數組,它們的運作方式與對象數組極為相似,只是前者直接包容了基本類型的數據值。
1. 基本數據類型集合
集合類只能容納對象句柄。但對一個數組,卻既可令其直接容納基本類型的數據,亦可容納指向對象的句柄。利用象Integer、Double之類的“封裝器”類,可將基本數據類型的值置入一個集合裡。但正如本章後面會在WordCount.java例子中講到的那樣,用於基本數據類型的封裝器類只是在某些場合下才能發揮作用。無論將基本類型的數據置入數組,還是將其封裝進入位於集合的一個類內,都涉及到執行效率的問題。顯然,若能創建和訪問一個基本數據類型數組,那麼比起訪問一個封裝數據的集合,前者的效率會高出許多。
當然,假如准備一種基本數據類型,同時又想要集合的靈活性(在需要的時候可自動擴展,騰出更多的空間),就不宜使用數組,必須使用由封裝的數據構成的一個集合。大家或許認為針對每種基本數據類型,都應有一種特殊類型的Vector。但Java並未提供這一特性。某些形式的建模機制或許會在某一天幫助Java更好地解決這個問題(注釋①)。
①:這兒是C++比Java做得好的一個地方,因為C++通過template關鍵字提供了對“參數化類型”的支持。