一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

歷史上的 Collection 類 ― 數(shù)組

 sttx 2008-12-02

2001 年 5 月 15 日

Java Collections (由 Apress 出版社出版) 一書由 John Zukowski 撰寫,介紹了 Java 2 平臺提供的 Collection 庫的詳細信息。其中包含關(guān)于歷史上的 Collection 類、Collection 框架以及可選的 Collection 庫等章節(jié)。這段節(jié)選(“歷史上的 Collection 類”部分的第一章)涵蓋有關(guān)數(shù)組的詳細信息 ― 用它們可以做些什么以及如何在使用數(shù)組的時候避免缺陷。

數(shù)組是在 Java 編程語言中定義的唯一的 Collection 支持。它們是按照 索引可訪問的順序或位置次序來存儲一組元素的對象。它們是 Object 類的子類,實現(xiàn)了 SerializableCloneable 兩種接口。這里沒有 .java 源文件讓您了解內(nèi)部是如何工作的?;旧?,您要創(chuàng)建一個有特定大小和元素類型的數(shù)組,然后填充它。

注意:因為數(shù)組是 Object 的子類,所以您可以同步數(shù)組變量并調(diào)用它的 wait()notify() 方法。

讓我們看一下使用數(shù)組對象能夠做些什么 ― 從基本用法和聲明開始,然后到復(fù)制和克隆。我們還將了解數(shù)組賦值、等同性檢查以及反射。

數(shù)組基礎(chǔ)知識

在討論聲明、創(chuàng)建、初始化以及復(fù)制數(shù)組的細節(jié)問題之前,讓我們先復(fù)習(xí)一個簡單的數(shù)組示例。當(dāng)創(chuàng)建一個 Java 應(yīng)用程序時, main() 方法有個唯一的字符串?dāng)?shù)組參數(shù): public static void main(String args []) 。編譯器并不在意您用什么參數(shù)名,只在意它是不是一個 String 對象的數(shù)組。

假設(shè)現(xiàn)在我們有個作為 String 對象數(shù)組的應(yīng)用程序的命令行參數(shù),我們可以觀察每個元素并打印它。在 Java 中,數(shù)組知道它們的大小,而且它們總是從位置零開始建立索引。因此,我們可以通過觀察數(shù)組專用的實例變量:length 來詢問這個數(shù)組有多大。下面的代碼展示了如何做到這一點:

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]);
                        }
                        }
                        }

注意:數(shù)組索引不能是 long 類型,因為只有非負整數(shù)才可以被用作索引,因此通過從 0 到 2 31-1 的索引范圍有效地將數(shù)組元素的數(shù)量限制在 2,147,483,648 或 2 31個。

因為在遍歷循環(huán)時數(shù)組大小不變,這就沒有必要在每次測試條件中查看 length 了,例如: for (int i=0; i<args.length; i++) 。事實上,在大多數(shù)情況下,不用遞增而用遞減遍歷循環(huán),并用零作測試條件通常會更快: for (int i=args.length-1; i>=0; i -) 。雖然在 JDK 1.1 和 1.2 發(fā)行版中,遞減計數(shù)與遞增計數(shù)相比只有相對較小的性能差異,但這個時間差異在 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));
                        }
                        }
                        

這個測試程序?qū)嶋H上是對 for 循環(huán)進行計時而不是對數(shù)組存取進行計時,因為這里沒有數(shù)組存取。

注意:在大多數(shù)情況下,在我的 400 MHz Windows NT 操作系統(tǒng)環(huán)境的 JDK 1.1 和 1.2 下,計數(shù)值低于 11,000 秒內(nèi)。但在 JDK1.3 下,并用 -classic 選項(沒有 JIT),計數(shù)值增加到 250,000 左右。甚至用 HotSpot VM with 1.3,計數(shù)值也只介于 19,000 和 30,000 之間。

如果你試圖訪問在數(shù)組頭部前面或者尾部以后的內(nèi)容,一個 ArrayIndexOutOfBoundsException 異常將被拋出。作為 IndexOutOfBoundsException 類的子類, ArrayIndexOutOfBoundsException 是一個運行時異常,如圖 2-1 所示。感到高興的是:這意味著您不必將數(shù)組存取放入 try-catch 代碼塊了。此外,因為查看數(shù)組上下界之外的內(nèi)容是一個運行時異常,所以您的程序會編譯得很好。而程序?qū)⒅辉谖覀冊噲D存取時拋出異常。


圖 2-1. ArrayIndexOutOfBoundsException 的類層次結(jié)構(gòu)
ArrayIndexOutOfBoundsException 的類層次結(jié)構(gòu)

注意:您無法關(guān)閉數(shù)組的邊界檢查。這是 Java 運行時安全體系的一部分,它確保無效內(nèi)存空間永遠不被存取。

下面的代碼演示了一種不正確的讀取命令行數(shù)組元素的方法:

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 示例相同,但對控制流使用異常處理是一個不好的編程習(xí)慣。異常處理應(yīng)該為異常情況保留。




聲明和創(chuàng)建數(shù)組

請記住,數(shù)組是按照可通過索引可訪問的順序來存儲一組元素的對象。這些元素可以是基本數(shù)據(jù)類型如 intfloat ,也可以是 Object 的任意類型。要聲明一個特殊類型的數(shù)組,只要向聲明添加方括號([])就可以了:

int[] variable;
                        

對于數(shù)組聲明來說,方括號可以處于三個位置中的其中一個:int[] variable、int []variable 以及 int variable[]。第一個位置說明變量是 int[] 類型的。后兩個說明變量是一個數(shù)組,該數(shù)組是 int 類型的。

注意:聽起來我們似乎是在討論語義學(xué)。但是,在聲明多個變量時,根據(jù)所使用的格式,會存在差異。格式 int[] var1, var2; 將會聲明兩個 int 類型的數(shù)組變量,而 int []var1,var2;int var1[], var2; 將聲明一個 int 類型的數(shù)組和另一個只是 int 類型的變量。

一旦聲明了一個數(shù)組,就可以創(chuàng)建這個數(shù)組并保存一個對它的引用。 new 操作符用來創(chuàng)建數(shù)組。在創(chuàng)建一個數(shù)組時,必須指定它的長度。長度一旦設(shè)定,就不能再更改。象下面的代碼所演示的那樣,長度可以被指定為常量或表達式。

int variable[] = new int [10];
                        

或者

int[] createArray(int size) {
                        return new int[size];
                        }
                        

注意:如果您試圖建立一個長度為負值的數(shù)組,將會拋出 NegativeArraySizeException 運行時異常。但零長度的數(shù)組是有效的。

您可以將數(shù)組的聲明和創(chuàng)建合并為一個步驟:

int variable[] = new int[10];
                        

警告:如果數(shù)組的創(chuàng)建導(dǎo)致了 OutOfMemoryError 錯誤的拋出,所有表示數(shù)組大小的表達式都已被求出了值。重要的是能否在指定數(shù)組大小的地方完成一次賦值。例如,如果表達式 int variable[] = new int[var1 = var2*var2] 會導(dǎo)致 OutOfMemoryError 拋出,那么在錯誤被拋出前要設(shè)置變量 var1 。

一旦數(shù)組被創(chuàng)建,您就可以填充該數(shù)組。這通常用一個 for 循環(huán)或者分別的賦值語句實現(xiàn)。例如,下面的代碼將創(chuàng)建并填充一個三元素的 names 數(shù)組:

String names = new String[3];
                        names [0] = "Leonardo";
                        names [1] = "da";
                        names [2] = "Vinci";
                        

基本數(shù)據(jù)類型數(shù)組 當(dāng)您創(chuàng)建了一個基本數(shù)據(jù)類型元素的數(shù)組,數(shù)組為那些元素保留了實際值。例如,圖 2-2 所示的是一個被變量 life 引用的包含六個整型元素的數(shù)組 (1452, 1472, 1483, 1495, 1503, 1519) 從棧和堆內(nèi)存的角度看,會是什么樣子。


圖 2-2. 基本數(shù)據(jù)類型數(shù)組的棧和堆內(nèi)存
基本數(shù)據(jù)類型數(shù)組的棧和堆內(nèi)存

對象數(shù)組
與基本數(shù)據(jù)類型數(shù)組不同,當(dāng)您創(chuàng)建了一個對象數(shù)組,它們不是存儲在一個實際的數(shù)組里。數(shù)組只是存儲了對實際對象的引用,除非被明確地初始化,否則最初每個引用都為空。(簡言之,更多的是依靠初始化。)圖 2-3 展示了 Object 元素組成的數(shù)組,其元素如下所示:

  • Leonardo da Vinci 出生的國家,意大利
  • 他的一張油畫圖像 The Baptism of Christ
  • 他關(guān)于直升機和降落傘的理論(畫圖)
  • 一張 Mona Lisa的圖像
  • 他逝世的國家,法國

圖 2-3 中要注意的關(guān)鍵是對象并不在數(shù)組中;在數(shù)組中的只是對對象的 引用。


圖 2-3. 對象數(shù)組的棧和堆內(nèi)存
對象數(shù)組的棧和堆內(nèi)存

多維數(shù)組
因為數(shù)組通過引用處理,這就沒有什么能阻止您讓一個數(shù)組元素引用另一個數(shù)組。當(dāng)一個數(shù)組引用另一個時,就得到一個 多維數(shù)組。向聲明行每添加一個維數(shù)就需要一組額外的方括號。例如,如果您想定義一個矩形,即二維數(shù)組,您可以用以下代碼行:

int coordinates[][];
                        

對一維數(shù)組來說,如果數(shù)組類型是基本數(shù)據(jù)類型的其中之一,一旦創(chuàng)建了數(shù)組就可以立即往里面存儲數(shù)值。對于多維數(shù)組,光聲明是不夠的。例如,下面兩行代碼會導(dǎo)致一個編譯時錯誤,因為數(shù)組變量從未被初始化:

int coordinates[][];
                        coordinates[0][0] = 2;
                        

但如果您在這兩句源代碼行之間創(chuàng)建一個數(shù)組(像 coordinates = new int [3][4]; ;),最后一行就會生效。

對于對象數(shù)組,創(chuàng)建一個多維數(shù)組會產(chǎn)生一個充滿空對象引用的數(shù)組。您還是需要創(chuàng)建對象以存儲到數(shù)組中。

因為多維數(shù)組最外層數(shù)組的每個元素都是對象引用,這就沒有必要讓數(shù)組成為一個矩形(或者一個為實現(xiàn)三維數(shù)組創(chuàng)建的立方體)。每個內(nèi)部數(shù)組可以有自己的大小。例如,下面的代碼演示了如何創(chuàng)建一個 float 類型的二維數(shù)組,其中內(nèi)部數(shù)組元素排列像一組保齡球瓶 — 第一排一個元素,第二排兩個,第三排三個,第四排四個:

float bowling[][] = new float[4][];
                        for (int i=0; i<4; i++) {
                        bowling[i] = new float[i+1];
                        }
                        

為幫助您形象地認識最終的數(shù)組,請參閱圖 2-4。


圖 2-4. 一個三角形的、像保齡球瓶的數(shù)組
一個三角形的、像保齡球瓶的數(shù)組

當(dāng)對一個多維數(shù)組進行存取時,在檢查右側(cè)的下一維表達式之前每一維表達式都已被完全求值。知道在存取數(shù)組時是否出現(xiàn)異常是很重要的。

注意:對于一維數(shù)組,雖然在語法上您可以將一個方括號放在數(shù)組變量的前面或后面( [index]name 或者 name[index] ),但對于多維數(shù)組,必須將方括號放在數(shù)組變量的后面,如 name[index1][index2] 。按照語法來看, [index1][index2]name[index1]name[index2] 如果在您的代碼中出現(xiàn),是不合法的,將會導(dǎo)致一個編譯時錯誤。但對于聲明來說,將方括號放在變量名前面( type [][]name )、后面( type name[][] )以及兩邊( type []name[] )是完全合法的。

請記住計算機內(nèi)存是線性的 ― 當(dāng)您對多維數(shù)組進行存取時,實際上是對內(nèi)存中的一維數(shù)組進行存取。如果您可以按照內(nèi)存存儲的次序?qū)λM行存取,就會最有效率。通常,如果所有的東西都在內(nèi)存中安排好了,就不會有什么關(guān)系,因為計算機內(nèi)存中跳躍存取也是很快的。但是,當(dāng)使用大型的數(shù)據(jù)結(jié)構(gòu)時,線性存取執(zhí)行情況最好而且避免了不必要的交換。此外,您可以通過將它們包裝成一維數(shù)組來模擬多維數(shù)組。對圖像經(jīng)常進行這樣處理。有兩種方法包裝一維數(shù)組,一種是以行為主的次序,數(shù)組一次填充一行;還有一種是以列為主的次序,往數(shù)組里放的是列。圖 2-5 顯示了這兩者的不同之處。


圖 2-5. 以行為主與以列為主的次序的比較
以行為主與以列為主的次序的比較

注意:在許多圖像處理例程中,像 ImageFilter 類里的 setPixels() 方法,您會發(fā)現(xiàn)二維的圖像數(shù)組轉(zhuǎn)換成了以行為主次序的一維數(shù)組,其中 Pixel(m, n) 轉(zhuǎn)化成了一維的下標(biāo) n * scansize + m。按照自上而下、自左至右的順序讀取整個圖像數(shù)據(jù)。

初始化數(shù)組當(dāng)數(shù)組首次被創(chuàng)建時,運行時環(huán)境會確保數(shù)組內(nèi)容被自動初始化為某些已知的(相對于未定義來說)值。對于未初始化的實例和類變量,數(shù)組內(nèi)容被初始化為:等價于 0 的數(shù)字、等價于 \u0000 的字符、布爾值 false 或者用于對象數(shù)組的 null。如表 2-1 所示。

表 2-1. 數(shù)組初始值

缺省值 數(shù)組
byte short int long
0.0 float double
\u0000 char
false boolean
null Object

當(dāng)您聲明一個數(shù)組時,您可以指定元素的初始值。這可通過在聲明位置等號后面的花括號 {} 之間提供由逗號分隔的數(shù)據(jù)列來完成。

例如,下面的代碼將會創(chuàng)建一個三元素的 names 數(shù)組:

String names[] = {"Leonardo", "da", "Vinci"};
                        

注意:如果提供了數(shù)組的初始化程序,則不必指定長度。數(shù)組長度會根據(jù)逗號分隔的數(shù)據(jù)列中元素的數(shù)量被自動地設(shè)置。

注意:Java 語言的語法允許在數(shù)組初始化程序塊中的最后一個元素后帶一個結(jié)尾逗號,如 {"Leonardo", "da", "Vinci",}。這并沒有將數(shù)組的長度變成 4,而仍舊保持為 3 。這種靈活性主要是出于對代碼生成器的考慮。

對于多維數(shù)組,您只要為每個新添加的維數(shù)使用一組額外的圓括號就可以了。例如,下面的代碼創(chuàng)建了一個 6 X 2 的關(guān)于年份和事件的數(shù)組。因為數(shù)組被聲明為 Object 元素的數(shù)組,就必須使用 Integer 包裝類將每個 int 基本數(shù)據(jù)類型的值存儲在里面。數(shù)組中的所有元素都必須是數(shù)組聲明的類型或這種類型的一個子類,在本例中就是 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")}
                        };
                        

注意:如果數(shù)組的類型是接口,則數(shù)組中的所有元素都必須實現(xiàn)這個接口。

從 Java 的第二個 dot-point 發(fā)行版(Java 1.1)開始,引入了 匿名數(shù)組的概念。雖然當(dāng)數(shù)組被聲明以后可以很容易初始化,但以后不能再用逗號分隔的數(shù)據(jù)列對數(shù)組進行重新初始化,除非聲明另一個變量來將新的數(shù)組存儲進去。這就是我們引入匿名數(shù)組的原因。有了匿名數(shù)組,您可以將數(shù)組重新初始化為一組新值,或者在您不想定義本地變量來存儲上述數(shù)組時將未命名的數(shù)組傳遞到方法中。

匿名數(shù)組的聲明與常規(guī)數(shù)組類似。但是,您要將包含在花括號里的用逗號分隔的一列值放在方括號的后面,而不是在方括號里指定一個長度。如下所示:

new type[] {comma-delimited-list}
                        

為了演示,以下幾行展示了如何調(diào)用方法并將匿名的 String 對象數(shù)組傳遞給它:

method(new String[] {"Leonardo", "da", "Vinci"});
                        

您會發(fā)現(xiàn)代碼生成器頻繁的使用匿名數(shù)組。

傳遞數(shù)組參數(shù)并返回值當(dāng)數(shù)組作為參數(shù)被傳遞到方法,對數(shù)組的引用就被傳遞了。這允許您修改數(shù)組的內(nèi)容,并在方法返回的時候讓調(diào)用例程看到數(shù)組的變化。此外,因為引用被傳遞,您還可以返回在方法中創(chuàng)建的數(shù)組,而且不必擔(dān)心在方法完成時垃圾收集器會釋放數(shù)組內(nèi)存。


使用數(shù)組可以做很多事。如果數(shù)組的初始大小已無法滿足您的需要,您就需要創(chuàng)建一個新的更大的數(shù)組,然后將原始元素復(fù)制到更大數(shù)組的相同位置。但是,如果您不需要將數(shù)組變大,而只希望在使原始數(shù)組保持原樣的基礎(chǔ)上修改數(shù)組元素,您必須創(chuàng)建數(shù)組的一個副本或克隆版本。

System 類中的 arraycopy() 方法允許您將元素從一個數(shù)組復(fù)制到另一個數(shù)組。當(dāng)進行這種復(fù)制時,目標(biāo)數(shù)組可以大些;但如果目標(biāo)數(shù)組比源數(shù)組小,就會在運行時拋出一個 ArrayIndexOutOfBoundsException 異常。 arraycopy() 方法用到 5 個參數(shù)(兩個用于數(shù)組,兩個用作數(shù)組的起始位置,還有一個表示復(fù)制元素的數(shù)量): public static void arraycopy (Object sourceArray,int sourceOffset,Object destinationArray,int destinationOffset,int numberOfElementsToCopy) 。 除了類型的兼容性,這里唯一的必要條件是已經(jīng)為目標(biāo)數(shù)組分配了內(nèi)存。

警告:當(dāng)在不同的數(shù)組之間復(fù)制元素時,如果源參數(shù)或目標(biāo)參數(shù)不是數(shù)組,或者它們的類型不兼容,就會拋出 ArrayStoreException 異常。不兼容的數(shù)組比如一個是基本數(shù)據(jù)類型數(shù)組而另一個是對象數(shù)組;或基本數(shù)據(jù)類型不同;或?qū)ο箢愋筒豢少x值。

為了演示,清單 2-2 采用一個整型數(shù)組并創(chuàng)建一個兩倍大的新數(shù)組。下面示例中的 doubleArray() 方法為我們做到了這一點:


清單 2-2. 將數(shù)組大小加倍
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;
                        }
                        }
                        

在獲得源數(shù)組的長度以后,先創(chuàng)建適當(dāng)大小的新數(shù)組,再把原始元素復(fù)制到新數(shù)組中相同的位置。在您了解數(shù)組的反射以后,您可以總結(jié)出將任意類型數(shù)組的大小加倍的方法。

執(zhí)行程序,生成如下輸出:

Original size: 5
                        New size: 10
                        Original size: 9
                        New size: 18
                        

注意:當(dāng)用 arraycopy() 進行復(fù)制時,如果您要將數(shù)組的子集復(fù)制到該數(shù)組內(nèi)的另一個區(qū)域,源數(shù)組和目標(biāo)數(shù)組可以是同一個。即使有些地方發(fā)生重疊也會生效。

因為數(shù)組實現(xiàn)了 Cloneable 接口,除了復(fù)制數(shù)組的區(qū)域以外,還可以克隆它們。 克隆包括創(chuàng)建一個同樣大小和類型的數(shù)組,并將所有的原始元素復(fù)制到新數(shù)組里。這和復(fù)制不同,復(fù)制要求您自己創(chuàng)建目標(biāo)數(shù)組并限定其大小。對基本數(shù)據(jù)類型的元素來說,新數(shù)組包含原始元素的副本,所以一個數(shù)組中元素的變化不會反映到它的副本中。但對于對象引用的情況,復(fù)制的只是引用。因而,數(shù)組的兩個副本將指向同一個對象。對該對象的更改將反映到兩個數(shù)組中。這叫做 淺拷貝或者 淺克隆

為了演示,下面的方法采用了一個整型數(shù)組并返回上述數(shù)組的一個副本。

static int[] cloneArray(int original[]) {
                        return (int[])original.clone();
                        }
                        

數(shù)組克隆用一個實際起作用的 public 方法覆蓋了被保護起來的 Object 方法,后者通常會拋出 CloneNotSupportedException 異常。


如果您不希望方法的調(diào)用程序修改底層數(shù)組結(jié)構(gòu),從方法返回數(shù)組克隆是非常有用的。您可以聲明數(shù)組為 final ,像下面的示例那樣:

final static int array[] = {1, 2, 3, 4, 5};
                        

不過聲明一個對象引用為 final (特別是這里的數(shù)組引用)并不限制您修改對象。它只限制您改變 final 變量引用的內(nèi)容。下面的代碼行導(dǎo)致了一個編譯錯誤:

array = new int[] {6, 7, 8, 9};
                        

不過改變個別的元素完全合法:

array[3] = 6;
                        

提示:另一種從方法“返回”不變數(shù)組的方法是將 Enumeration 或者 Iterator 返回到數(shù)組,這比返回實際數(shù)組要好。任一接口都提供對單個元素的存取而不需要改變整個數(shù)組或要求制作整個數(shù)組的副本。您會在 Java Collections的后續(xù)章節(jié)學(xué)到更多的有關(guān)這些接口的知識。


數(shù)組賦值

數(shù)組賦值和變量賦值的操作一樣。如果變量 x 是對 y 數(shù)組的引用,如果 z 類型的變量可以賦值給 y,那么 x 就可以成為對 z 的引用。例如,設(shè)想 y 是個 AWT Component 類,而 z 是個 AWT Button 類。因為 Button 變量可以被賦給 Component 變量,則 Button 數(shù)組可以被賦給 Component 數(shù)組:

Button buttons[] = {
                        new Button("One"),
                        new Button("Two"),
                        new Button("Three")};
                        Component components[] = buttons;
                        

當(dāng)進行這樣的賦值時,兩個變量的 bottons 和 componets 引用的是內(nèi)存的同一個堆空間。如圖 2-6 所示。改變一個數(shù)組中的元素將會造成兩個數(shù)組的元素都改變。


圖 2-6. 數(shù)組賦值后的共享內(nèi)存
數(shù)組賦值后的共享內(nèi)存

如果,將一個數(shù)組變量賦值給一個超類數(shù)組變量之后(正如在前一個示例中將 botton 數(shù)組賦值給 component 數(shù)組變量一樣),您接著試圖將一個不同的子類實例放入數(shù)組,一個 ArrayStoreException 異常就會被拋出。繼續(xù)前一個示例,如果您試圖將一個 Canvas 對象放入 components 數(shù)組,一個 ArrayStoreException 異常就會被拋出。即使 components 數(shù)組被聲明為 Component 對象數(shù)組也是如此,因為 components 數(shù)組是對 Button 對象數(shù)組的特別引用, Canvas 對象無法被存到數(shù)組中。這是個運行時異常,因為從類型安全編譯器看,實際賦值是合法的。




檢查數(shù)組等同性

檢查兩個數(shù)組之間的等同性可以用兩種方式中的一種,這取決于您想要查找的等同性類型。是數(shù)組變量指向內(nèi)存的同一位置因而指向同一數(shù)組?或是兩數(shù)組中的元素相等?

檢查對同一內(nèi)存空間的兩個引用可用由兩個等號組成的運算符(==)來實現(xiàn)。例如,前面的 components 和 buttons 變量在這種情況下將是相等的,因為其中一個是另一個的引用:

components == buttons  //true

但是,如果您將數(shù)組和它的克隆版本比較,那么用 ==,它們是不會相等的。因為這兩個數(shù)組雖然有相同的元素,但處于不同的內(nèi)存空間,它們是不同的。為了讓一個數(shù)組的克隆版本與原數(shù)組相等,您必須使用 java.util.Arrays 類中的 equals() 方法。

String[] clone = (String[]) strarray.clone();
                        boolean b1 = Arrays.equals(strarray, clone);   //Yes,they're equal
                        

這就會檢查每個元素的等同性。在參數(shù)是對象數(shù)組的情況下,每個對象的 equals() 方法會被用來檢查等同性。 Arrays.equals() 方法也為非克隆的數(shù)組服務(wù)。




數(shù)組反射

如果因為某種原因,您并不確定參數(shù)或?qū)ο笫遣皇菙?shù)組,您可以檢索對象的 Class 對象并詢問它。 Class 類的 isArray() 方法將會告訴您。一旦您知道擁有了一個數(shù)組,您可以詢問 ClassgetComponentType() 方法,您實際擁有的是什么類型的數(shù)組。如果 isArray() 方法返回 false,那么 getComponentType() 方法返回空。否則返回元素的 Class 類型。如果數(shù)組是多維的,您可以遞歸調(diào)用 isArray() 。它將仍只包含一個 component 類型。此外,您可以用在 java.lang.reflect 包里找到的 Array 類的 getLength() 方法獲取數(shù)組的長度。

為了演示,清單 2-3 顯示了傳遞給 main() 方法的參數(shù)是 java.lang.String 對象的數(shù)組,其中數(shù)組長度由命令行參數(shù)的個數(shù)確定:


清單 2-3. 使用反射檢查數(shù)組類型和長度
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() 用于前面定義的 buttonscomponents 變量調(diào)用,每個都會表明數(shù)組是 java.awt.Button 類型。

如果不使用 isArray()getComponentType() 方法,而且試圖打印數(shù)組的 Class 類型,您將獲得一個包含 [ ,后面跟著一個字母和類名(如果是個基本數(shù)據(jù)類型就沒有類名)的字符串。例如,如果您試圖打印出上述 printType() 方法中的類型變量,您將獲得 class [Ljava.lang.String; 作為輸出。

除了詢問一個對象是不是數(shù)組以及是什么類型的數(shù)組之外,您還可以在運行時用 java.lang.reflect.Array class 創(chuàng)建數(shù)組。這對于創(chuàng)建一般實用例程非常有用,這些例程執(zhí)行數(shù)組任務(wù),比如將大小加倍。(我們會立即回到那一點。)

要創(chuàng)建一個新數(shù)組,使用 ArraynewInstance() 方法,它有兩種變化形式。對于一維數(shù)組您通常將使用較簡單版本,它的執(zhí)行方式如語句 new type [length] 所示,并作為對象返回數(shù)組: public static Object newInstance(Class type, int length) 。例如,下面的代碼創(chuàng)建一個五個整數(shù)空間大小的數(shù)組:

int array[] = (int[])Array.newInstance(int.class, 5);
                        

注意:要為基本數(shù)據(jù)類型指定 Class 對象,只要在基本數(shù)據(jù)類型名末尾添加 .class 就可以了。您還可以使用包裝類中的 TYPE 變量,如 Integer.TYPE。

newInstance() 方法中的第二種變化形式要求維數(shù)被指定為整型數(shù)組: public static Object newInstance(Class type,int dimensions []) 。在創(chuàng)建一個一維數(shù)組的最簡單的情況下,您將創(chuàng)建只有一個元素的數(shù)組。換句話說,如果您要創(chuàng)建包含五個整數(shù)的相同數(shù)組,您需要創(chuàng)建一個單個元素 5 的數(shù)組并傳遞到 newInstance() 方法,而不是傳遞整數(shù)值 5。

int dimensions[] = {5};
                        int array[] = (int[])Array.newInstance(int.class, dimensions);
                        

在您只需要創(chuàng)建一個矩形數(shù)組的時候,您就可以將每個數(shù)組長度填充到這個 dimensions 數(shù)組中。例如,下面的代碼與創(chuàng)建一個 3 X 4 的整數(shù)數(shù)組等價。

int dimensions[] = {3, 4};
                        int array[][] = (int[][])Array.newInstance(int.class, dimensions);
                        

但是,如果您需要創(chuàng)建一個非矩形數(shù)組,您將需要多次調(diào)用 newInstance() 方法。第一次調(diào)用將定義外部數(shù)組的長度,并獲得一個看上去很古怪的類參數(shù)([].class 適用于元素為 float 類型的數(shù)組)。每個后續(xù)調(diào)用將定義每個內(nèi)部數(shù)組的長度。例如,下面演示了如何創(chuàng)建一個元素為 float 類型的數(shù)組,其內(nèi)部數(shù)組的大小設(shè)置像一組保齡球瓶:第一排一個元素,第二排兩個,第三排三個,第四排四個。為了幫您將這種情況形象化,讓我們回顧早先在圖 2-4 展示的三角形數(shù)組。

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)建了數(shù)組,您還可以獲取和設(shè)置數(shù)組元素。不過通常不會這樣做,除非鍵盤上的方括號鍵失靈或者您在動態(tài)的編程環(huán)境(程序被創(chuàng)建時數(shù)組名未知)中工作。 如表 2-2 所示, Array 類有一系列的 getter 和 setter 方法用來獲取和設(shè)置數(shù)組元素。使用什么方法取決于您處理的數(shù)組類型。

表 2-2. 數(shù)組 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() 方法。如果數(shù)組是一個基本數(shù)據(jù)類型數(shù)組, get() 方法的返回值或 set() 方法的值參數(shù)將被包裝到用于基本數(shù)據(jù)類型的包裝類中,像裝著一個 int 數(shù)組的 Integer 類那樣。

清單 2-4 提供了一個如何創(chuàng)建、填充以及顯示數(shù)組信息的完整示例。方括號只在 main() 方法的聲明中使用。


清單 2-4. 使用反射創(chuàng)建、填充和顯示數(shù)組
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);
                        }
                        }
                        }
                        

運行時,輸出將如下所示(盡管隨機數(shù)會不同):

Array of: int
                        Array size: 3
                        Position: 0, value: -54541791
                        Position: 1, value: -972349058
                        Position: 2, value: 1224789416
                        

讓我們返回到早先的,創(chuàng)建一個將數(shù)組大小加倍的方法的示例。既然您知道如何獲取數(shù)組的類型,您可以創(chuàng)建一種方法用來將任意類型數(shù)組的大小加倍。這個方法確保我們能在獲取它的長度和類型之前得到數(shù)組。然后在復(fù)制原來的那組元素之前,它將新數(shù)組的大小加倍。

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;
                        }
                        




字符數(shù)組

在總結(jié)我們對 Java 數(shù)組的討論之前還要提到最后一件事:與 C 和 C++ 不同的是,字符數(shù)組在 Java 語言中不是字符串。雖然使用 String 構(gòu)造器(采用 char 類型的對象數(shù)組)和 StringtoCharArray() 方法能很容易的在 Stringchar[] 之間轉(zhuǎn)換,但它們的確不同。

盡管字節(jié)數(shù)組是另一種情況,但它們也不是字符串,試圖在 byte[]String 之間轉(zhuǎn)換要做一些工作,因為 Java 語言中的字符串是基于 Unicode 的,并且有 16 位的寬度。您需要告訴字符串構(gòu)造器編碼模式。表 2-3 演示了 1.3 平臺提供的基本編碼模式。如需其它擴展的編碼模式的清單,請參閱 http://java./j2se/1.3/docs/guide/intl/encoding.doc.html上的在線清單。它們因 JDK 版本的不同而不同。

表 2-3. 字節(jié)到字符的基本編碼模式

名稱 描述
ASCII 美國信息交換標(biāo)準(zhǔn)碼
Cp1252 Windows Latin-1
ISO8859_1 ISO 8859-1,拉丁字母表一號
UnicodeBig 16 位統(tǒng)一碼轉(zhuǎn)換格式,大尾數(shù)字節(jié)順序,帶字節(jié)順序記號
UnicodeBigUnmarked 16 位統(tǒng)一碼轉(zhuǎn)換格式,大尾數(shù)字節(jié)順序
UnicodeLittle 16 位統(tǒng)一碼轉(zhuǎn)換格式,小尾數(shù)字節(jié)順序,帶字節(jié)順序記號
UnicodeLittleUnmarked 16 位統(tǒng)一碼轉(zhuǎn)換格式,小尾數(shù)字節(jié)順序
UTF16 16 位統(tǒng)一碼轉(zhuǎn)換格式,字節(jié)順序由強制的初始字節(jié)順序字節(jié)記號指定
UTF8 8 位統(tǒng)一碼轉(zhuǎn)換格式

如果您的確指定了編碼模式,您必須把對 String 構(gòu)造器的調(diào)用放置在 try-catch 代碼塊中,因為如果指定的編碼模式無效,一個 UnsupportedEncodingException 異常就會被拋出。

如果您只是用 ASCII 碼字符,您的確不用為此過多擔(dān)心。把 byte [] 傳遞到 String 構(gòu)造器而不使用任何編碼模式的參數(shù),這樣就可以使用平臺缺省的編碼模式,這已經(jīng)足夠了。當(dāng)然為保險起見,您可以總是只傳遞 ASCII 作為編碼模式。

注意:為檢查您平臺上的缺省值,請察看 _file.encoding_ 系統(tǒng)屬性。




總結(jié)

操作 Java 中的數(shù)組似乎非常容易,不過要充分利用它們的功能,還要知道很多事情。雖然基礎(chǔ)的數(shù)組聲明和用法非常簡單,但當(dāng)操作基本數(shù)據(jù)類型的數(shù)組、對象數(shù)組以及多維數(shù)組時還需要有不同的考慮。數(shù)組的初始化不需要很復(fù)雜,但是引入了匿名數(shù)組,事件就復(fù)雜了。

一旦有了數(shù)組,您需要多花點心思想想如何把它用到最好。當(dāng)傳遞數(shù)組到方法時您需要特別小心,因為它們是通過引用傳遞的。如果數(shù)組的初始大小已無法滿足您的需要,您需要構(gòu)造一個帶有額外空間的副本。數(shù)組克隆讓您順利的復(fù)制而無需擔(dān)心 final 關(guān)鍵字的特性。當(dāng)把數(shù)組賦給其它變量時,請當(dāng)心不要遇到 ArrayStoreException 異常,因為這個異常在運行時很難處理。數(shù)組的等同性檢查既包括對于相同內(nèi)存空間的檢查,又包括對已賦值的元素的等同性的檢查。通過神奇的 Java 反射,您可以處理正好是數(shù)組的對象。在這一章里您了解的最后一件事是如何在字節(jié)數(shù)組和字符串之間轉(zhuǎn)換,以及字節(jié)數(shù)組如何像在其它語言中一樣缺省情況下不是字符串。



參考資料



關(guān)于作者

 

John Zukowski 和 JZ Ventures從事戰(zhàn)略性的 Java 咨詢,提供指導(dǎo)、結(jié)構(gòu)框架建議、設(shè)計、調(diào)試以及代碼評審以及開發(fā)定制的解決方案。他曾獲得東北大學(xué)計算機系和數(shù)學(xué)系的學(xué)士學(xué)位,并獲得了 Johns Hopkins 大學(xué)計算機系的碩士學(xué)位。請通過 jaz@zukowski.net與他聯(lián)系。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    欧美区一区二区在线观看| 国产91人妻精品一区二区三区| 国产精品白丝久久av| 精品人妻一区二区三区四在线| 国产盗摄精品一区二区视频| 91人妻人人揉人人澡人| 欧美日韩国产黑人一区| 欧美日韩国产综合特黄| 久热香蕉精品视频在线播放| 国产欧美日韩精品自拍| 午夜国产精品国自产拍av| 绝望的校花花间淫事2| 男人和女人草逼免费视频 | 熟女体下毛荫荫黑森林自拍| 欧美一级黄片欧美精品| 亚洲欧美国产中文色妇| 国产欧美一区二区久久| 亚洲午夜精品视频观看| 四季精品人妻av一区二区三区 | 最好看的人妻中文字幕| 成年女人下边潮喷毛片免费| 国产精品国产亚洲看不卡 | 91偷拍与自偷拍精品| 国产一区二区三区丝袜不卡| 国产伦精品一区二区三区精品视频 | 欧美午夜一级艳片免费看| 欧美日韩乱码一区二区三区| 国产又大又硬又粗又黄| 日韩三极片在线免费播放| 久久成人国产欧美精品一区二区| 亚洲人午夜精品射精日韩| 日韩一区二区三区高清在| 午夜日韩在线观看视频| 精品熟女少妇av免费久久野外| 91插插插外国一区二区婷婷| 欧美日韩国产精品黄片| 免费大片黄在线观看国语| 国产欧美一区二区另类精品| 欧美日韩欧美国产另类| 日本女优一区二区三区免费| 日韩蜜桃一区二区三区|