| 2001 年 5 月 15 日 Java Collections (由 Apress 出版社出版) 一書由 John Zukowski 撰寫,介紹了 Java 2 平臺提供的 Collection 庫的詳細信息。其中包含關于歷史上的 Collection 類、Collection 框架以及可選的 Collection 庫等章節(jié)。這段節(jié)選(“歷史上的 Collection 類”部分的第一章)涵蓋有關數組的詳細信息 ― 用它們可以做些什么以及如何在使用數組的時候避免缺陷。  數組是在 Java 編程語言中定義的唯一的 Collection 支持。它們是按照 索引可訪問的順序或位置次序來存儲一組元素的對象。它們是 Object類的子類,實現了Serializable和Cloneable兩種接口。這里沒有 .java 源文件讓您了解內部是如何工作的。基本上,您要創(chuàng)建一個有特定大小和元素類型的數組,然后填充它。 注意:因為數組是 Object的子類,所以您可以同步數組變量并調用它的wait()和notify()方法。 
             讓我們看一下使用數組對象能夠做些什么 ― 從基本用法和聲明開始,然后到復制和克隆。我們還將了解數組賦值、等同性檢查以及反射。 數組基礎知識 在討論聲明、創(chuàng)建、初始化以及復制數組的細節(jié)問題之前,讓我們先復習一個簡單的數組示例。當創(chuàng)建一個 Java 應用程序時, main()方法有個唯一的字符串數組參數:public static void main(String args [])。編譯器并不在意您用什么參數名,只在意它是不是一個String對象的數組。 假設現在我們有個作為 String對象數組的應用程序的命令行參數,我們可以觀察每個元素并打印它。在 Java 中,數組知道它們的大小,而且它們總是從位置零開始建立索引。因此,我們可以通過觀察數組專用的實例變量:length 來詢問這個數組有多大。下面的代碼展示了如何做到這一點: 
                
                    
                        | public class ArrayArgs {
                        public static void main (String args[]) {
                        for (int i=0, n=args.length; i<n; i++) {
                        System.out.println("Arg " + i +":" + args[i]);
                        }
                        }
                        } |  
 注意:數組索引不能是 long類型,因為只有非負整數才可以被用作索引,因此通過從 0 到 2 31-1 的索引范圍有效地將數組元素的數量限制在 2,147,483,648 或 2 31個。 
             因為在遍歷循環(huán)時數組大小不變,這就沒有必要在每次測試條件中查看 length 了,例如: for (int i=0; i<args.length; i++)。事實上,在大多數情況下,不用遞增而用遞減遍歷循環(huán),并用零作測試條件通常會更快:for (int i=args.length-1; i>=0; i -)。雖然在 JDK 1.1 和 1.2 發(fā)行版中,遞減計數與遞增計數相比只有相對較小的性能差異,但這個時間差異在 1.3 發(fā)行版中卻更為顯著。為在您的平臺上演示這種速度差異,請嘗試運行清單 2-1 中的程序,測試循環(huán)“max int”(int 類型的最大值,即 2 的 31 次方減 1)次所需要的時間: 清單 2-1. 對循環(huán)性能計時
 
 
                
                    
                        | public class TimeArray {
                        public static void main (String args[]) {
                        int something = 2;
                        long startTime = System.currentTimeMillis();
                        for (int i=0, n=Integer.MAX_VALUE; i<n; i++) {
                        something =- something;
                        }
                        long midTime = System.currentTimeMillis();
                        for (int i=Integer.MAX_VALUE-1; i>=0; i-) {
                        something = -something;
                        }
                        long endTime = System.currentTimeMillis();
                        System.out.println("Increasing Delta: " + (midTime - startTime));
                        System.out.println("Decreasing Delta: " + (endTime - midTime));
                        }
                        }
                         |  
 這個測試程序實際上是對 for 循環(huán)進行計時而不是對數組存取進行計時,因為這里沒有數組存取。 注意:在大多數情況下,在我的 400 MHz Windows NT 操作系統(tǒng)環(huán)境的 JDK 1.1 和 1.2 下,計數值低于 11,000 秒內。但在 JDK1.3 下,并用 -classic選項(沒有 JIT),計數值增加到 250,000 左右。甚至用 HotSpot VM with 1.3,計數值也只介于 19,000 和 30,000 之間。 
             如果你試圖訪問在數組頭部前面或者尾部以后的內容,一個 ArrayIndexOutOfBoundsException異常將被拋出。作為IndexOutOfBoundsException類的子類,ArrayIndexOutOfBoundsException是一個運行時異常,如圖 2-1 所示。感到高興的是:這意味著您不必將數組存取放入 try-catch 代碼塊了。此外,因為查看數組上下界之外的內容是一個運行時異常,所以您的程序會編譯得很好。而程序將只在我們試圖存取時拋出異常。 圖 2-1. ArrayIndexOutOfBoundsException 的類層次結構
 
   
 注意:您無法關閉數組的邊界檢查。這是 Java 運行時安全體系的一部分,它確保無效內存空間永遠不被存取。  
             下面的代碼演示了一種不正確的讀取命令行數組元素的方法: 
                
                    
                        | public class ArrayArgs2 {
                        public static void main (String args[]) {
                        try {
                        int i=0;
                        do {
                        System.out.println("Arg " + i + ": " + args[i++]);
                        } while (true);
                        } catch (ArrayIndexOutOfBoundsException ignored) {
                        }
                        }
                        }
                         |  
 雖然在功能上與前一個 ArrayArgs 示例相同,但對控制流使用異常處理是一個不好的編程習慣。異常處理應該為異常情況保留。 
 
 
 聲明和創(chuàng)建數組 請記住,數組是按照可通過索引可訪問的順序來存儲一組元素的對象。這些元素可以是基本數據類型如 int或float,也可以是Object的任意類型。要聲明一個特殊類型的數組,只要向聲明添加方括號([])就可以了: 
 對于數組聲明來說,方括號可以處于三個位置中的其中一個:int[] variable、int []variable 以及 int variable[]。第一個位置說明變量是 int[] 類型的。后兩個說明變量是一個數組,該數組是 int類型的。 注意:聽起來我們似乎是在討論語義學。但是,在聲明多個變量時,根據所使用的格式,會存在差異。格式 int[] var1, var2;將會聲明兩個int類型的數組變量,而int []var1,var2;或int var1[], var2;將聲明一個int類型的數組和另一個只是int類型的變量。 一旦聲明了一個數組,就可以創(chuàng)建這個數組并保存一個對它的引用。 new操作符用來創(chuàng)建數組。在創(chuàng)建一個數組時,必須指定它的長度。長度一旦設定,就不能再更改。象下面的代碼所演示的那樣,長度可以被指定為常量或表達式。 
             
                
                    
                        | int variable[] = new int [10];
                         |  
 或者 
                
                    
                        | int[] createArray(int size) {
                        return new int[size];
                        }
                         |  
 注意:如果您試圖建立一個長度為負值的數組,將會拋出 NegativeArraySizeException運行時異常。但零長度的數組是有效的。 
             您可以將數組的聲明和創(chuàng)建合并為一個步驟: 
                
                    
                        | int variable[] = new int[10];
                         |  
 警告:如果數組的創(chuàng)建導致了 OutOfMemoryError錯誤的拋出,所有表示數組大小的表達式都已被求出了值。重要的是能否在指定數組大小的地方完成一次賦值。例如,如果表達式int variable[] = new int[var1 = var2*var2]會導致OutOfMemoryError拋出,那么在錯誤被拋出前要設置變量var1。 
             一旦數組被創(chuàng)建,您就可以填充該數組。這通常用一個 for 循環(huán)或者分別的賦值語句實現。例如,下面的代碼將創(chuàng)建并填充一個三元素的 names 數組: 
                
                    
                        | String names = new String[3];
                        names [0] = "Leonardo";
                        names [1] = "da";
                        names [2] = "Vinci";
                         |  
 基本數據類型數組 當您創(chuàng)建了一個基本數據類型元素的數組,數組為那些元素保留了實際值。例如,圖 2-2 所示的是一個被變量 life引用的包含六個整型元素的數組 (1452, 1472, 1483, 1495, 1503, 1519) 從棧和堆內存的角度看,會是什么樣子。 圖 2-2. 基本數據類型數組的棧和堆內存
 
   
 對象數組 與基本數據類型數組不同,當您創(chuàng)建了一個對象數組,它們不是存儲在一個實際的數組里。數組只是存儲了對實際對象的引用,除非被明確地初始化,否則最初每個引用都為空。(簡言之,更多的是依靠初始化。)圖 2-3 展示了
 Object元素組成的數組,其元素如下所示: 
                Leonardo da Vinci 出生的國家,意大利
                他的一張油畫圖像 The Baptism of Christ
                他關于直升機和降落傘的理論(畫圖)
                一張 Mona Lisa的圖像
                他逝世的國家,法國  圖 2-3 中要注意的關鍵是對象并不在數組中;在數組中的只是對對象的 引用。  圖 2-3. 對象數組的棧和堆內存
 
   
 多維數組 因為數組通過引用處理,這就沒有什么能阻止您讓一個數組元素引用另一個數組。當一個數組引用另一個時,就得到一個 多維數組。向聲明行每添加一個維數就需要一組額外的方括號。例如,如果您想定義一個矩形,即二維數組,您可以用以下代碼行:
 
 對一維數組來說,如果數組類型是基本數據類型的其中之一,一旦創(chuàng)建了數組就可以立即往里面存儲數值。對于多維數組,光聲明是不夠的。例如,下面兩行代碼會導致一個編譯時錯誤,因為數組變量從未被初始化: 
                
                    
                        | int coordinates[][];
                        coordinates[0][0] = 2;
                         |  
 但如果您在這兩句源代碼行之間創(chuàng)建一個數組(像 coordinates = new int [3][4];;),最后一行就會生效。 對于對象數組,創(chuàng)建一個多維數組會產生一個充滿空對象引用的數組。您還是需要創(chuàng)建對象以存儲到數組中。 因為多維數組最外層數組的每個元素都是對象引用,這就沒有必要讓數組成為一個矩形(或者一個為實現三維數組創(chuàng)建的立方體)。每個內部數組可以有自己的大小。例如,下面的代碼演示了如何創(chuàng)建一個 float類型的二維數組,其中內部數組元素排列像一組保齡球瓶 — 第一排一個元素,第二排兩個,第三排三個,第四排四個: 
                
                    
                        | float bowling[][] = new float[4][];
                        for (int i=0; i<4; i++) {
                        bowling[i] = new float[i+1];
                        }
                         |  
 為幫助您形象地認識最終的數組,請參閱圖 2-4。 圖 2-4. 一個三角形的、像保齡球瓶的數組
 
   
 當對一個多維數組進行存取時,在檢查右側的下一維表達式之前每一維表達式都已被完全求值。知道在存取數組時是否出現異常是很重要的。 注意:對于一維數組,雖然在語法上您可以將一個方括號放在數組變量的前面或后面( [index]name或者name[index]),但對于多維數組,必須將方括號放在數組變量的后面,如name[index1][index2]。按照語法來看,[index1][index2]name和[index1]name[index2]如果在您的代碼中出現,是不合法的,將會導致一個編譯時錯誤。但對于聲明來說,將方括號放在變量名前面(type [][]name)、后面(type name[][])以及兩邊(type []name[])是完全合法的。 
             請記住計算機內存是線性的 ― 當您對多維數組進行存取時,實際上是對內存中的一維數組進行存取。如果您可以按照內存存儲的次序對它進行存取,就會最有效率。通常,如果所有的東西都在內存中安排好了,就不會有什么關系,因為計算機內存中跳躍存取也是很快的。但是,當使用大型的數據結構時,線性存取執(zhí)行情況最好而且避免了不必要的交換。此外,您可以通過將它們包裝成一維數組來模擬多維數組。對圖像經常進行這樣處理。有兩種方法包裝一維數組,一種是以行為主的次序,數組一次填充一行;還有一種是以列為主的次序,往數組里放的是列。圖 2-5 顯示了這兩者的不同之處。 圖 2-5. 以行為主與以列為主的次序的比較
 
   
 注意:在許多圖像處理例程中,像 ImageFilter類里的setPixels()方法,您會發(fā)現二維的圖像數組轉換成了以行為主次序的一維數組,其中Pixel(m, n)轉化成了一維的下標 n * scansize + m。按照自上而下、自左至右的順序讀取整個圖像數據。 
             初始化數組當數組首次被創(chuàng)建時,運行時環(huán)境會確保數組內容被自動初始化為某些已知的(相對于未定義來說)值。對于未初始化的實例和類變量,數組內容被初始化為:等價于 0 的數字、等價于 \u0000 的字符、布爾值 false 或者用于對象數組的 null。如表 2-1 所示。  表 2-1. 數組初始值
             
                
                    
                        | 缺省值 | 數組 |  
                        |  | byte short int long |  
                        | 0.0 | float double |  
                        | \u0000 | char |  
                        | false | boolean |  
                        | null | Object |  
             當您聲明一個數組時,您可以指定元素的初始值。這可通過在聲明位置等號后面的花括號 {} 之間提供由逗號分隔的數據列來完成。 例如,下面的代碼將會創(chuàng)建一個三元素的 names 數組: 
                
                    
                        | String names[] = {"Leonardo", "da", "Vinci"};
                         |  
 注意:如果提供了數組的初始化程序,則不必指定長度。數組長度會根據逗號分隔的數據列中元素的數量被自動地設置。 注意:Java 語言的語法允許在數組初始化程序塊中的最后一個元素后帶一個結尾逗號,如 {"Leonardo", "da", "Vinci",}。這并沒有將數組的長度變成 4,而仍舊保持為 3 。這種靈活性主要是出于對代碼生成器的考慮。  
             對于多維數組,您只要為每個新添加的維數使用一組額外的圓括號就可以了。例如,下面的代碼創(chuàng)建了一個 6 X 2 的關于年份和事件的數組。因為數組被聲明為 Object元素的數組,就必須使用Integer包裝類將每個int基本數據類型的值存儲在里面。數組中的所有元素都必須是數組聲明的類型或這種類型的一個子類,在本例中就是Object,即使所有的元素都是子類也是如此。 
                
                    
                        | Object events [][] = {
                        {new Integer(1452), new Birth("Italy")},
                        {new Integer(1472), new Painting("baptismOfChrist.jpg")},
                        {new Integer(1483), new Theory("Helicopter")},
                        {new Integer(1495), new Theory("Parachute")},
                        {new Integer(1503), new Painting("monaLisa.jpg")},
                        {new Integer(1519), new Death("France")}
                        };
                         |  
 注意:如果數組的類型是接口,則數組中的所有元素都必須實現這個接口。  從 Java 的第二個 dot-point 發(fā)行版(Java 1.1)開始,引入了 匿名數組的概念。雖然當數組被聲明以后可以很容易初始化,但以后不能再用逗號分隔的數據列對數組進行重新初始化,除非聲明另一個變量來將新的數組存儲進去。這就是我們引入匿名數組的原因。有了匿名數組,您可以將數組重新初始化為一組新值,或者在您不想定義本地變量來存儲上述數組時將未命名的數組傳遞到方法中。  匿名數組的聲明與常規(guī)數組類似。但是,您要將包含在花括號里的用逗號分隔的一列值放在方括號的后面,而不是在方括號里指定一個長度。如下所示: 
                
                    
                        | new type[] {comma-delimited-list}
                         |  
 為了演示,以下幾行展示了如何調用方法并將匿名的 String對象數組傳遞給它: 
                
                    
                        | method(new String[] {"Leonardo", "da", "Vinci"});
                         |  
 您會發(fā)現代碼生成器頻繁的使用匿名數組。 傳遞數組參數并返回值當數組作為參數被傳遞到方法,對數組的引用就被傳遞了。這允許您修改數組的內容,并在方法返回的時候讓調用例程看到數組的變化。此外,因為引用被傳遞,您還可以返回在方法中創(chuàng)建的數組,而且不必擔心在方法完成時垃圾收集器會釋放數組內存。  
 使用數組可以做很多事。如果數組的初始大小已無法滿足您的需要,您就需要創(chuàng)建一個新的更大的數組,然后將原始元素復制到更大數組的相同位置。但是,如果您不需要將數組變大,而只希望在使原始數組保持原樣的基礎上修改數組元素,您必須創(chuàng)建數組的一個副本或克隆版本。 System類中的arraycopy()方法允許您將元素從一個數組復制到另一個數組。當進行這種復制時,目標數組可以大些;但如果目標數組比源數組小,就會在運行時拋出一個ArrayIndexOutOfBoundsException異常。arraycopy()方法用到 5 個參數(兩個用于數組,兩個用作數組的起始位置,還有一個表示復制元素的數量):public static void arraycopy (Object sourceArray,int sourceOffset,Object destinationArray,int destinationOffset,int numberOfElementsToCopy)。 除了類型的兼容性,這里唯一的必要條件是已經為目標數組分配了內存。
 警告:當在不同的數組之間復制元素時,如果源參數或目標參數不是數組,或者它們的類型不兼容,就會拋出 ArrayStoreException 異常。不兼容的數組比如一個是基本數據類型數組而另一個是對象數組;或基本數據類型不同;或對象類型不可賦值。  為了演示,清單 2-2 采用一個整型數組并創(chuàng)建一個兩倍大的新數組。下面示例中的 doubleArray()方法為我們做到了這一點: 清單 2-2. 將數組大小加倍
 
 
                
                    
                        | public class DoubleArray {
                        public static void main (String args ]) {
                        int array1[] = {1, 2, 3, 4, 5};
                        int array2[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
                        System.out.println("Original size: " + array1.length);
                        System.out.println("New size: " + doubleArray(array1).length);
                        System.out.println("Original size: " + array2.length);
                        System.out.println("New size: " + doubleArray(array2).length);
                        }
                        static int[] doubleArray(int original[]) {
                        int length = original.length;
                        int newArray[] = new int[length*2];
                        System.arraycopy(original, 0, newArray, 0, length);
                        return newArray;
                        }
                        }
                         |  
 在獲得源數組的長度以后,先創(chuàng)建適當大小的新數組,再把原始元素復制到新數組中相同的位置。在您了解數組的反射以后,您可以總結出將任意類型數組的大小加倍的方法。 執(zhí)行程序,生成如下輸出: 
                
                    
                        | Original size: 5
                        New size: 10
                        Original size: 9
                        New size: 18
                         |  
 注意:當用 arraycopy()進行復制時,如果您要將數組的子集復制到該數組內的另一個區(qū)域,源數組和目標數組可以是同一個。即使有些地方發(fā)生重疊也會生效。 
             因為數組實現了 Cloneable接口,除了復制數組的區(qū)域以外,還可以克隆它們。 克隆包括創(chuàng)建一個同樣大小和類型的數組,并將所有的原始元素復制到新數組里。這和復制不同,復制要求您自己創(chuàng)建目標數組并限定其大小。對基本數據類型的元素來說,新數組包含原始元素的副本,所以一個數組中元素的變化不會反映到它的副本中。但對于對象引用的情況,復制的只是引用。因而,數組的兩個副本將指向同一個對象。對該對象的更改將反映到兩個數組中。這叫做 淺拷貝或者 淺克隆。 為了演示,下面的方法采用了一個整型數組并返回上述數組的一個副本。 
                
                    
                        | static int[] cloneArray(int original[]) {
                        return (int[])original.clone();
                        }
                         |  
 數組克隆用一個實際起作用的 public 方法覆蓋了被保護起來的 Object方法,后者通常會拋出CloneNotSupportedException異常。 
 如果您不希望方法的調用程序修改底層數組結構,從方法返回數組克隆是非常有用的。您可以聲明數組為 final,像下面的示例那樣: 
                
                    
                        | final static int array[] = {1, 2, 3, 4, 5};
                         |  
 不過聲明一個對象引用為 final(特別是這里的數組引用)并不限制您修改對象。它只限制您改變final變量引用的內容。下面的代碼行導致了一個編譯錯誤: 
                
                    
                        | array = new int[] {6, 7, 8, 9};
                         |  
 不過改變個別的元素完全合法: 
 提示:另一種從方法“返回”不變數組的方法是將 Enumeration或者Iterator返回到數組,這比返回實際數組要好。任一接口都提供對單個元素的存取而不需要改變整個數組或要求制作整個數組的副本。您會在 Java Collections的后續(xù)章節(jié)學到更多的有關這些接口的知識。 
 數組賦值 數組賦值和變量賦值的操作一樣。如果變量 x 是對 y 數組的引用,如果 z 類型的變量可以賦值給 y,那么 x 就可以成為對 z 的引用。例如,設想 y 是個 AWT Component類,而 z 是個 AWTButton類。因為Button變量可以被賦給Component變量,則Button數組可以被賦給Component數組: 
                
                    
                        | Button buttons[] = {
                        new Button("One"),
                        new Button("Two"),
                        new Button("Three")};
                        Component components[] = buttons;
                         |  
 當進行這樣的賦值時,兩個變量的 bottons 和 componets 引用的是內存的同一個堆空間。如圖 2-6 所示。改變一個數組中的元素將會造成兩個數組的元素都改變。 圖 2-6. 數組賦值后的共享內存
 
   
 如果,將一個數組變量賦值給一個超類數組變量之后(正如在前一個示例中將 botton 數組賦值給 component 數組變量一樣),您接著試圖將一個不同的子類實例放入數組,一個 ArrayStoreException異常就會被拋出。繼續(xù)前一個示例,如果您試圖將一個Canvas對象放入 components 數組,一個ArrayStoreException異常就會被拋出。即使 components 數組被聲明為Component對象數組也是如此,因為 components 數組是對Button對象數組的特別引用,Canvas對象無法被存到數組中。這是個運行時異常,因為從類型安全編譯器看,實際賦值是合法的。 
 
 檢查數組等同性 檢查兩個數組之間的等同性可以用兩種方式中的一種,這取決于您想要查找的等同性類型。是數組變量指向內存的同一位置因而指向同一數組?或是兩數組中的元素相等? 檢查對同一內存空間的兩個引用可用由兩個等號組成的運算符(==)來實現。例如,前面的 components 和 buttons 變量在這種情況下將是相等的,因為其中一個是另一個的引用: 
                
                    
                        | components == buttons  //true |  
 但是,如果您將數組和它的克隆版本比較,那么用 ==,它們是不會相等的。因為這兩個數組雖然有相同的元素,但處于不同的內存空間,它們是不同的。為了讓一個數組的克隆版本與原數組相等,您必須使用 java.util.Arrays類中的equals()方法。 
                
                    
                        | String[] clone = (String[]) strarray.clone();
                        boolean b1 = Arrays.equals(strarray, clone);   //Yes,they're equal
                         |  
 這就會檢查每個元素的等同性。在參數是對象數組的情況下,每個對象的 equals()方法會被用來檢查等同性。Arrays.equals()方法也為非克隆的數組服務。 
 
 數組反射 如果因為某種原因,您并不確定參數或對象是不是數組,您可以檢索對象的 Class對象并詢問它。Class類的isArray()方法將會告訴您。一旦您知道擁有了一個數組,您可以詢問Class的getComponentType() 方法,您實際擁有的是什么類型的數組。如果isArray()方法返回 false,那么getComponentType()方法返回空。否則返回元素的 Class 類型。如果數組是多維的,您可以遞歸調用isArray()。它將仍只包含一個 component 類型。此外,您可以用在java.lang.reflect包里找到的Array類的getLength()方法獲取數組的長度。 為了演示,清單 2-3 顯示了傳遞給 main()方法的參數是java.lang.String對象的數組,其中數組長度由命令行參數的個數確定: 清單 2-3. 使用反射檢查數組類型和長度
 
 
                
                    
                        | public class ArrayReflection {
                        public static void main (String args[]) {
                        printType(args);
                        }
                        private static void printType (Object object) {
                        Class type = object.getClass();
                        if (type.isArray()) {
                        Class elementType = type.getComponentType();
                        System.out.println("Array of: " + elementType);
                        System.out.println(" Length: " + Array.getLength(object));
                        }
                        }
                        }
                         |  
 注意:如果 printType()用于前面定義的buttons和components變量調用,每個都會表明數組是java.awt.Button類型。 如果不使用 isArray()和getComponentType()方法,而且試圖打印數組的 Class 類型,您將獲得一個包含 [ ,后面跟著一個字母和類名(如果是個基本數據類型就沒有類名)的字符串。例如,如果您試圖打印出上述printType()方法中的類型變量,您將獲得class [Ljava.lang.String;作為輸出。 除了詢問一個對象是不是數組以及是什么類型的數組之外,您還可以在運行時用 java.lang.reflect.Array class創(chuàng)建數組。這對于創(chuàng)建一般實用例程非常有用,這些例程執(zhí)行數組任務,比如將大小加倍。(我們會立即回到那一點。) 要創(chuàng)建一個新數組,使用 Array的newInstance()方法,它有兩種變化形式。對于一維數組您通常將使用較簡單版本,它的執(zhí)行方式如語句new type [length]所示,并作為對象返回數組:public static Object newInstance(Class type, int length)。例如,下面的代碼創(chuàng)建一個五個整數空間大小的數組: 
                
                    
                        | int array[] = (int[])Array.newInstance(int.class, 5);
                         |  
 注意:要為基本數據類型指定 Class對象,只要在基本數據類型名末尾添加 .class 就可以了。您還可以使用包裝類中的 TYPE 變量,如 Integer.TYPE。 newInstance()方法中的第二種變化形式要求維數被指定為整型數組:public static Object newInstance(Class type,int dimensions [])。在創(chuàng)建一個一維數組的最簡單的情況下,您將創(chuàng)建只有一個元素的數組。換句話說,如果您要創(chuàng)建包含五個整數的相同數組,您需要創(chuàng)建一個單個元素 5 的數組并傳遞到newInstance()方法,而不是傳遞整數值 5。
 
                
                    
                        | int dimensions[] = {5};
                        int array[] = (int[])Array.newInstance(int.class, dimensions);
                         |  
 在您只需要創(chuàng)建一個矩形數組的時候,您就可以將每個數組長度填充到這個 dimensions 數組中。例如,下面的代碼與創(chuàng)建一個 3 X 4 的整數數組等價。 
                
                    
                        | int dimensions[] = {3, 4};
                        int array[][] = (int[][])Array.newInstance(int.class, dimensions);
                         |  
 但是,如果您需要創(chuàng)建一個非矩形數組,您將需要多次調用 newInstance()方法。第一次調用將定義外部數組的長度,并獲得一個看上去很古怪的類參數([].class 適用于元素為float類型的數組)。每個后續(xù)調用將定義每個內部數組的長度。例如,下面演示了如何創(chuàng)建一個元素為float類型的數組,其內部數組的大小設置像一組保齡球瓶:第一排一個元素,第二排兩個,第三排三個,第四排四個。為了幫您將這種情況形象化,讓我們回顧早先在圖 2-4 展示的三角形數組。 
                
                    
                        | float bowling[][] = (float[][])Array.newInstance(float[].class, 4);
                        for (int i=0; i<4; i++) {
                        bowling[i] = (float[])Array.newInstance(float.class, i+1);
                        }
                         |  
 一旦在運行時創(chuàng)建了數組,您還可以獲取和設置數組元素。不過通常不會這樣做,除非鍵盤上的方括號鍵失靈或者您在動態(tài)的編程環(huán)境(程序被創(chuàng)建時數組名未知)中工作。 如表 2-2 所示, Array類有一系列的 getter 和 setter 方法用來獲取和設置數組元素。使用什么方法取決于您處理的數組類型。 表 2-2. 數組 getter 和 setter 方法
             
                
                    
                        | Getter 方法 | Setter 方法 |  
                        | get(Object array, int index) | set(Object array, int index, Object value) |  
                        | getBoolean(Object array, int index) | setBoolean(Object array, int index, boolean value) |  
                        | getByte(Object array, int index) | setByte(Object array, int index, byte value) |  
                        | getChar(Object array, int index) | setChar(Object array, int index, char value) |  
                        | getDouble(Object array, int index) | setDouble(Object array, int index, double value) |  
                        | getFloat(Object array, int index) | setFloat(Object array, int index, float value) |  
                        | getInt(Object array, int index) | setInt(Object array, int index, int value) |  
                        | getLong(Object array, int index) | setLong(Object array, int index, long value) |  
                        | getShort(Object array, int index) | setShort(Object array, int index, short value) |  注意:您可以一直使用 get()和set()方法。如果數組是一個基本數據類型數組,get()方法的返回值或set()方法的值參數將被包裝到用于基本數據類型的包裝類中,像裝著一個int數組的Integer類那樣。 清單 2-4 提供了一個如何創(chuàng)建、填充以及顯示數組信息的完整示例。方括號只在 main()方法的聲明中使用。 清單 2-4. 使用反射創(chuàng)建、填充和顯示數組
 
 
                
                    
                        | import java.lang.reflect.Array;
                        import java.util.Random;
                        public class ArrayCreate {
                        public static void main (String args[]) {
                        Object array = Array.newInstance(int.class, 3);
                        printType(array);
                        fillArray(array);
                        displayArray(array);
                        }
                        private static void printType (Object object) {
                        Class type = object.getClass();
                        if (type.isArray()) {
                        Class elementType = type.getComponentType();
                        System.out.println("Array of: " + elementType);
                        System.out.println("Array size: " + Array.getLength(object));
                        }
                        }
                        private static void fillArray(Object array) {
                        int length = Array.getLength(array);
                        Random generator = new Random(System.currentTimeMillis());
                        for (int i=0; i<length; i++) {
                        int random = generator.nextInt();
                        Array.setInt(array, i, random);
                        }
                        }
                        private static void displayArray(Object array) {
                        int length = Array.getLength(array);
                        for (int i=0; i<length; i++) {
                        int value = Array.getInt(array, i);
                        System.out.println("Position: " + i +", value: " + value);
                        }
                        }
                        }
                         |  
 運行時,輸出將如下所示(盡管隨機數會不同): 
                
                    
                        | Array of: int
                        Array size: 3
                        Position: 0, value: -54541791
                        Position: 1, value: -972349058
                        Position: 2, value: 1224789416
                         |  
 讓我們返回到早先的,創(chuàng)建一個將數組大小加倍的方法的示例。既然您知道如何獲取數組的類型,您可以創(chuàng)建一種方法用來將任意類型數組的大小加倍。這個方法確保我們能在獲取它的長度和類型之前得到數組。然后在復制原來的那組元素之前,它將新數組的大小加倍。 
                
                    
                        | static Object doubleArray(Object original) {
                        Object returnValue = null;
                        Class type = original.getClass();
                        if (type.isArray()) {
                        int length = Array.getLength(original);
                        Class elementType = type.getComponentType();
                        returnValue = Array.newInstance(elementType, length*2);
                        System.arraycopy(original, 0, returnValue, 0, length);
                        }
                        return returnValue;
                        }
                         |  
 
 
 字符數組 在總結我們對 Java 數組的討論之前還要提到最后一件事:與 C 和 C++ 不同的是,字符數組在 Java 語言中不是字符串。雖然使用 String構造器(采用char類型的對象數組)和String的toCharArray()方法能很容易的在String和char[]之間轉換,但它們的確不同。 盡管字節(jié)數組是另一種情況,但它們也不是字符串,試圖在 byte[]與String之間轉換要做一些工作,因為 Java 語言中的字符串是基于 Unicode 的,并且有 16 位的寬度。您需要告訴字符串構造器編碼模式。表 2-3 演示了 1.3 平臺提供的基本編碼模式。如需其它擴展的編碼模式的清單,請參閱 http://java./j2se/1.3/docs/guide/intl/encoding.doc.html上的在線清單。它們因 JDK 版本的不同而不同。 表 2-3. 字節(jié)到字符的基本編碼模式 
             
                
                    
                        | 名稱 | 描述 |  
                        | ASCII | 美國信息交換標準碼 |  
                        | Cp1252 | Windows Latin-1 |  
                        | ISO8859_1 | ISO 8859-1,拉丁字母表一號 |  
                        | UnicodeBig | 16 位統(tǒng)一碼轉換格式,大尾數字節(jié)順序,帶字節(jié)順序記號 |  
                        | UnicodeBigUnmarked | 16 位統(tǒng)一碼轉換格式,大尾數字節(jié)順序 |  
                        | UnicodeLittle | 16 位統(tǒng)一碼轉換格式,小尾數字節(jié)順序,帶字節(jié)順序記號 |  
                        | UnicodeLittleUnmarked | 16 位統(tǒng)一碼轉換格式,小尾數字節(jié)順序 |  
                        | UTF16 | 16 位統(tǒng)一碼轉換格式,字節(jié)順序由強制的初始字節(jié)順序字節(jié)記號指定 |  
                        | UTF8 | 8 位統(tǒng)一碼轉換格式 |  如果您的確指定了編碼模式,您必須把對 String構造器的調用放置在 try-catch 代碼塊中,因為如果指定的編碼模式無效,一個UnsupportedEncodingException異常就會被拋出。 如果您只是用 ASCII 碼字符,您的確不用為此過多擔心。把 byte []傳遞到String構造器而不使用任何編碼模式的參數,這樣就可以使用平臺缺省的編碼模式,這已經足夠了。當然為保險起見,您可以總是只傳遞 ASCII 作為編碼模式。 注意:為檢查您平臺上的缺省值,請察看 _file.encoding_ 系統(tǒng)屬性。  
 
 總結 操作 Java 中的數組似乎非常容易,不過要充分利用它們的功能,還要知道很多事情。雖然基礎的數組聲明和用法非常簡單,但當操作基本數據類型的數組、對象數組以及多維數組時還需要有不同的考慮。數組的初始化不需要很復雜,但是引入了匿名數組,事件就復雜了。 一旦有了數組,您需要多花點心思想想如何把它用到最好。當傳遞數組到方法時您需要特別小心,因為它們是通過引用傳遞的。如果數組的初始大小已無法滿足您的需要,您需要構造一個帶有額外空間的副本。數組克隆讓您順利的復制而無需擔心 final關鍵字的特性。當把數組賦給其它變量時,請當心不要遇到ArrayStoreException異常,因為這個異常在運行時很難處理。數組的等同性檢查既包括對于相同內存空間的檢查,又包括對已賦值的元素的等同性的檢查。通過神奇的 Java 反射,您可以處理正好是數組的對象。在這一章里您了解的最后一件事是如何在字節(jié)數組和字符串之間轉換,以及字節(jié)數組如何像在其它語言中一樣缺省情況下不是字符串。 
 
 參考資料  
 
 關于作者 
                
                    
                        |  |  
                        |   |  | John Zukowski 和 JZ Ventures從事戰(zhàn)略性的 Java 咨詢,提供指導、結構框架建議、設計、調試以及代碼評審以及開發(fā)定制的解決方案。他曾獲得東北大學計算機系和數學系的學士學位,并獲得了 Johns Hopkins 大學計算機系的碩士學位。請通過 jaz@zukowski.net與他聯系。  |  |