第08部分:數(shù)組

數(shù)組是一種特殊的對(duì)象,保存零個(gè)或多個(gè)基本類型或引用類型的值。這些值是數(shù)組的元素,是通過(guò)所在位置或索引引用的無(wú)名變量。數(shù)組的類型通過(guò)元素的類型表示,數(shù)組中的所有元素必須都屬于這個(gè)類型。數(shù)組元素的編號(hào)從零開(kāi)始,有效的索引范圍是零到元素?cái)?shù)量減一。例如,索引為 1 的元素,是數(shù)組中的第二個(gè)元素。數(shù)組中的元素?cái)?shù)量是數(shù)組的長(zhǎng)度。數(shù)組的長(zhǎng)度在創(chuàng)建時(shí)指定,從此就不能改變。

數(shù)組中元素的類型可以是任何有效的 Java 類型,包括數(shù)組類型。也就是說(shuō),Java 支持由數(shù)組組成的數(shù)組,實(shí)現(xiàn)多維數(shù)組。Java 不支持其他語(yǔ)言中的矩陣式多維數(shù)組。

數(shù)組的類型

數(shù)組的類型和類一樣,也是引用類型。數(shù)組的實(shí)例和類的實(shí)例一樣,也是對(duì)象。和類不同的是,數(shù)組的類型不用定義,只需在元素類型后面加上一對(duì)中括號(hào)即可。例如,下述代碼聲明了三種不同類型的數(shù)組:

byte b; // byte是基本類型

byte[] arrayOfBytes; // byte[]是由byte類型的值組成的數(shù)組

byte[][] arrayOfArrayOfBytes; // byte[][]是由byte[]類型的值組成的數(shù)組

String[] points; // String[]是由字符串組成的數(shù)組

數(shù)組的長(zhǎng)度不是數(shù)組類型的一部分。例如,聲明一個(gè)方法,并且期望傳入恰好由四個(gè) int類型的值組成的數(shù)組,是不可能的。如果方法的參數(shù)類型是 int[],調(diào)用時(shí)傳入的數(shù)組可以包含任意個(gè)元素(包括零個(gè))。


數(shù)組類型不是類,但數(shù)組實(shí)例是對(duì)象。這意味著,數(shù)組從 java.lang.Object 類繼承了方法。數(shù)組實(shí)現(xiàn)了 Cloneable 接口,而且覆蓋了 clone() 方法,確保數(shù)組始終能被復(fù)制,而且 clone() 方法從不拋出 CloneNotSupportedException 異常。數(shù)組還實(shí)現(xiàn)了 Serializable 接口,所以只要數(shù)組中元素的類型能被序列化,數(shù)組就能被序列化。而且,所有數(shù)組都有一個(gè)名為 length 的字段,這個(gè)字段的修飾符是 public final int,表示數(shù)組中元素的數(shù)量。


因?yàn)閿?shù)組擴(kuò)展自 Object 類,而且實(shí)現(xiàn)了 Cloneable 和 Serializable 接口,所以任何數(shù)組類型都能放大轉(zhuǎn)換成這三種類型中的任何一種。而且,特定的數(shù)組類型還能放大轉(zhuǎn)換成其他數(shù)組類型。如果數(shù)組中的元素類型是引用類型 T,而且 T 能指定給類型 S,那么數(shù)組類型T[] 就能指定給數(shù)組類型 S[]。注意,基本類型的數(shù)組不能放大轉(zhuǎn)換。例如,下述代碼展示了合法的數(shù)組放大轉(zhuǎn)換:

String[] arrayOfStrings; // 創(chuàng)建字符串?dāng)?shù)組

int[][] arrayOfArraysOfInt; // 創(chuàng)建int二維數(shù)組

// String可以指定給Object,

// 因此String[]可以指定給Object[]

Object[] oa = arrayOfStrings;

// String實(shí)現(xiàn)了Comparable接口

// 因此String[]可以視作Comparable[]

Comparable[] ca = arrayOfStrings;

// int[]是Object類的對(duì)象,因此int[][]可以指定給Object[]

Object[] oa2 = arrayOfArraysOfInt;

//所有數(shù)組都是可以復(fù)制和序列化的對(duì)象

Object o = arrayOfStrings;

Cloneable c = arrayOfArraysOfInt;

Serializable s = arrayOfArraysOfInt[0];

因?yàn)閿?shù)組類型可以放大轉(zhuǎn)換成另一種數(shù)組類型,所以編譯時(shí)和運(yùn)行時(shí)數(shù)組的類型并不總是一樣。這種放大轉(zhuǎn)換叫作“數(shù)組協(xié)變”(array covariance)。

現(xiàn)代標(biāo)準(zhǔn)認(rèn)為這是歷史遺留的不合理功能,因?yàn)榫幾g時(shí)和運(yùn)行時(shí)得出的類型不一致。


把引用類型的值存儲(chǔ)在數(shù)組元素中之前,編譯器通常必須插入運(yùn)行時(shí)檢查,確保運(yùn)行時(shí)這個(gè)值的類型和數(shù)組元素的類型匹配。如果運(yùn)行時(shí)檢查失敗,會(huì)拋出 ArrayStoreException異常。

與C語(yǔ)言兼容的句法

如前所示,指定數(shù)組類型的方法是在元素類型后加上一對(duì)中括號(hào)。為了兼容 C 和 C++,Java 還支持一種聲明變量的句法:中括號(hào)放在變量名后面,元素類型后面可以放也可以不放中括號(hào)。這種句法可用于局部變量,字段和方法的參數(shù)。例如:

// 這行代碼聲明類型為int,int[]和int[][]的局部變量

int justOne, arrayOfThem[], arrayOfArrays[][];

// 這三行代碼聲明的字段屬于同一種數(shù)組類型

public String[][] aas1;? ?// 推薦使用的Java句法

public String aas2[][];? ?// C語(yǔ)言的句法

public String[] aas3[];? ?// 令人困惑的混用句法

// 這個(gè)方法簽名包含兩個(gè)類型相同的參數(shù)

public static double dotProduct(double[] x, double y[]) { ... }

注意:這種兼容句法極其少見(jiàn),不要使用。


創(chuàng)建和初始化數(shù)組

在 Java 中,使用 new 關(guān)鍵字創(chuàng)建數(shù)組,就像創(chuàng)建對(duì)象一樣。數(shù)組類型沒(méi)有構(gòu)造方法,但創(chuàng)建數(shù)組時(shí)要指定長(zhǎng)度,在中括號(hào)里使用非負(fù)整數(shù)指定所需的數(shù)組大小:

// 創(chuàng)建一個(gè)能保存1024個(gè)byte類型數(shù)據(jù)的新數(shù)組

byte[] buffer = new byte[1024];

//創(chuàng)建一個(gè)能保存50個(gè)字符串引用的數(shù)組

String[] lines = new String[50];

使用這種句法創(chuàng)建的數(shù)組,每個(gè)元素都會(huì)自動(dòng)初始化,初始值和類中的字段默認(rèn)值相同:boolean 類型元素的初始值是 false,char 類型元素的初始值是 \u0000,整數(shù)元素的初始值是 0,浮點(diǎn)數(shù)元素的初始值是 0.0,引用類型元素的初始值是 null。

創(chuàng)建數(shù)組的表達(dá)式也能用來(lái)創(chuàng)建和初始化多維數(shù)組。這種句法稍微復(fù)雜一些。


數(shù)組初始化程序

若想在一個(gè)表達(dá)式中創(chuàng)建數(shù)組并初始化其中的元素,不要指定數(shù)組的長(zhǎng)度,在方括號(hào)后面跟著一對(duì)花括號(hào),在花括號(hào)里寫(xiě)入一些逗號(hào)分隔的表達(dá)式。當(dāng)然了,每個(gè)表達(dá)式的返回值類型必須能指定給數(shù)組元素的類型。創(chuàng)建的數(shù)組長(zhǎng)度和表達(dá)式的數(shù)量相等。這組表達(dá)式的最后一個(gè)后面可以加上逗號(hào),但沒(méi)必要這么做。例如:

String[] greetings = new String[] { "Hello", "Hi", "Howdy" };

int[] smallPrimes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, };

注意,這種句法無(wú)需把數(shù)組賦值給變量就能創(chuàng)建、初始化和使用數(shù)組。某種意義上,這種創(chuàng)建數(shù)組的表達(dá)式相當(dāng)于匿名數(shù)組字面量。下面是幾個(gè)示例:

// 調(diào)用一個(gè)方法,傳入一個(gè)包含兩個(gè)字符串的匿名數(shù)組字面量

String response = askQuestion("Do you want to quit?",

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?????new String[] {"Yes", "No"});

// 調(diào)用另一個(gè)方法,傳入匿名對(duì)象組成的匿名數(shù)組

double d = computeAreaOfTriangle(new Point[] { new Point(1,2),

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new Point(3,4),

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? new Point(3,2) });

如果數(shù)組初始化程序是變量聲明的一部分,可以省略 new 關(guān)鍵字和元素類型,在花括號(hào)里列出所需的元素:

String[] greetings = { "Hello", "Hi", "Howdy" };

int[] powersOfTwo = {1, 2, 4, 8, 16, 32, 64, 128};

數(shù)組字面量在程序運(yùn)行時(shí),而不是程序編譯時(shí),創(chuàng)建和初始化。例如下述數(shù)組字面量:

int[] perfectNumbers = {6, 28};

編譯得到的 Java 字節(jié)碼和下面的代碼相同:

int[] perfectNumbers = new int[2];

perfectNumbers[0] = 6;

perfectNumbers[1] = 28;


Java 在運(yùn)行時(shí)初始化數(shù)組有個(gè)重要的推論:數(shù)組初始化程序中的表達(dá)式可能會(huì)在運(yùn)行時(shí)計(jì)算,而且不一定非要使用編譯時(shí)常量。例如:

Point[] points = { circle1.getCenterPoint(), circle2.getCenterPoint() };






使用數(shù)組

創(chuàng)建數(shù)組后就可以開(kāi)始使用了。下面說(shuō)明訪問(wèn)元素的基本方法,以及常見(jiàn)的數(shù)組用法,例如迭代數(shù)組中的元素,復(fù)制數(shù)組或數(shù)組的一部分。

訪問(wèn)數(shù)組中的元素

數(shù)組中的元素是變量。如果元素出現(xiàn)在表達(dá)式中,其計(jì)算結(jié)果是這個(gè)元素中保存的值。如果元素出現(xiàn)在賦值運(yùn)算符的左邊,會(huì)把一個(gè)新值保存到這個(gè)元素中。不過(guò),元素和普通的變量不同,它沒(méi)有名字,只有編號(hào)。數(shù)組中的元素使用方括號(hào)訪問(wèn)。假如 a 是一個(gè)表達(dá)式,其計(jì)算結(jié)果為一個(gè)數(shù)組引用,那么可以使用 a[i] 索引數(shù)組,并引用某個(gè)元素。其中,i 是整數(shù)字面量或計(jì)算結(jié)果為 int 類型值的表達(dá)式。例如:

// 創(chuàng)建一個(gè)由兩個(gè)字符串組成的數(shù)組

String[] responses = new String[2];

responses[0] = "Yes"; // 設(shè)定數(shù)組的第一個(gè)元素

responses[1] = "No"; // 設(shè)定數(shù)組的第二個(gè)元素

// 讀取這個(gè)數(shù)組中的元素

System.out.println(question + " (" + responses[0] + "/" +

? ? ? ? ? ? ? ? ? ? ? ? responses[1] + " ): ");

// 數(shù)組引用和數(shù)組索引都可以是復(fù)雜的表達(dá)式

double datum = data.getMatrix()[data.row() * data.numColumns() +

? ? ? ? ? ? ? ? ? ? ? ? data.column()];

數(shù)組的索引表達(dá)式必須是 int 類型,或能放大轉(zhuǎn)換成 int 的類型:byte、short,甚至是char。數(shù)組的索引顯然不能是 boolean、float 或 double 類型。還記得嗎,數(shù)組的 length字段是 int 類型,所以數(shù)組中的元素?cái)?shù)量不能超過(guò) Integer.MAX_VALUE。如果使用 long 類型的表達(dá)式索引數(shù)組,即便運(yùn)行時(shí)表達(dá)式的返回值在 int 類型的取值范圍內(nèi),也會(huì)導(dǎo)致編譯出錯(cuò)。


數(shù)組的邊界

數(shù)組 a 的第一個(gè)元素是 a[0],第二個(gè)元素是 a[1],最后一個(gè)元素是 a[a.length-1]。

使用數(shù)組時(shí)常見(jiàn)的錯(cuò)誤是索引太小(負(fù)數(shù))或太大(大于或等于數(shù)組的長(zhǎng)度)。在 C 或C++ 等語(yǔ)言中,如果訪問(wèn)起始索引之前或結(jié)尾索引之后的元素,會(huì)導(dǎo)致無(wú)法預(yù)料的行為,而且在不同的調(diào)用和不同的平臺(tái)中有所不同。這種問(wèn)題不一定會(huì)被捕獲,如果沒(méi)捕獲,可能過(guò)一段時(shí)間才會(huì)發(fā)現(xiàn)。因?yàn)樵?Java 中容易編寫(xiě)錯(cuò)誤的索引代碼,所以運(yùn)行時(shí)每次訪問(wèn)數(shù)組都會(huì)做檢查,確保得到能預(yù)料的結(jié)果。如果數(shù)組的索引太小或太大,Java 會(huì)立即拋出ArrayIndexOutOfBoundsException 異常。


迭代數(shù)組

為了在數(shù)組上執(zhí)行某種操作,經(jīng)常要編寫(xiě)循環(huán),迭代數(shù)組中的每個(gè)元素。這種操作通常使用 for 循環(huán)完成。例如,下述代碼計(jì)算整數(shù)數(shù)組中的元素之和:

int[] primes = { 2, 3, 5, 7, 11, 13, 17, 19, 23 };

int sumOfPrimes = 0;

for(int i = 0; i < primes.length; i++)

????????????????sumOfPrimes += primes[i];

這種 for 循環(huán)結(jié)構(gòu)很有特色,會(huì)經(jīng)常見(jiàn)到。Java 還支持遍歷句法,前面已經(jīng)介紹過(guò)。上述求和代碼可以改寫(xiě)成下述簡(jiǎn)潔的代碼:

for(int p : primes)?

????????????????sumOfPrimes += p;


復(fù)制數(shù)組

所有數(shù)組類型都實(shí)現(xiàn)了 Cloneable 接口,任何數(shù)組都能調(diào)用 clone() 方法復(fù)制自己。注意,返回值必須校正成適當(dāng)?shù)臄?shù)組類型。不過(guò),在數(shù)組上調(diào)用 clone() 方法不會(huì)拋出CloneNotSupportedException 異常:

int[] data = { 1, 2, 3 };

int[] copy = (int[]) data.clone();

clone() 方法執(zhí)行的是淺復(fù)制。如果數(shù)組的元素是引用類型,那么只復(fù)制引用,而不復(fù)制引用的對(duì)象。因?yàn)檫@種復(fù)制是淺復(fù)制,所以任何數(shù)組都能被復(fù)制,就算元素類型沒(méi)有實(shí)現(xiàn)Cloneable 接口也行。

不過(guò),有時(shí)只想把一個(gè)現(xiàn)有數(shù)組中的元素復(fù)制到另一個(gè)現(xiàn)有數(shù)組中。System.arraycopy()方法的目的就是高效完成這種操作。你可以假定 Java 虛擬機(jī)實(shí)現(xiàn)會(huì)在底層硬件中使用高速塊復(fù)制操作執(zhí)行這個(gè)方法。

arraycopy() 方法的作用簡(jiǎn)單明了,但使用起來(lái)有些難度,因?yàn)橐涀∥鍌€(gè)參數(shù)。第一個(gè)參數(shù)是想從中復(fù)制元素的源數(shù)組;第二個(gè)參數(shù)是源數(shù)組中起始元素的索引;第三個(gè)參數(shù)是目標(biāo)數(shù)組;第四個(gè)參數(shù)是目標(biāo)索引;第五個(gè)參數(shù)是要復(fù)制的元素?cái)?shù)量。


就算重疊復(fù)制同一個(gè)數(shù)組,arraycopy() 方法也能正確運(yùn)行。例如,把數(shù)組 a 中索引為 0 的元素刪除后,想把索引為 1 到 n 的元素向左移,把索引變成 0 到 n-1,可以這么做:

System.arraycopy(a, 1, a, 0, n);


數(shù)組的實(shí)用方法

java.util.Arrays 類中包含很多處理數(shù)組的靜態(tài)實(shí)用方法。這些方法中大多數(shù)都高度重載,有針對(duì)各種基本類型數(shù)組的版本,也有針對(duì)對(duì)象數(shù)組的版本。排序和搜索數(shù)組時(shí),sort()和binarySearch() 方法特別有用。equals() 方法用于比較兩個(gè)數(shù)組的內(nèi)容。如果想把數(shù)組的內(nèi)容轉(zhuǎn)換成一個(gè)字符串,例如用于調(diào)試或記錄日志,Arrays.toString() 方法很有用。

Arrays 類中還包含能正確處理多維數(shù)組的方法,例如 deepEquals()、deepHashCode() 和deepToString()。





多維數(shù)組

前面已經(jīng)見(jiàn)過(guò),數(shù)組類型的寫(xiě)法是在元素類型后面加一對(duì)方括號(hào)。char 類型元素組成的數(shù)組是 char[] 類型,由 char[] 類型元素組成的數(shù)組是 char[][] 類型。如果數(shù)組的元素也是數(shù)組,我們說(shuō)這個(gè)數(shù)組是多維數(shù)組。要想使用多維數(shù)組,需要了解一些其他細(xì)節(jié)。

假如想使用多維數(shù)組表示乘法表:

int[][] products; // 乘法表

每對(duì)方括號(hào)表示一個(gè)維度,所以這是個(gè)二維數(shù)組。若想訪問(wèn)這個(gè)二維數(shù)組中的某個(gè) int元素,必須指定兩個(gè)索引值,一個(gè)維度一個(gè)。假設(shè)這個(gè)數(shù)組確實(shí)被初始化成一個(gè)乘法表,那么元素中存儲(chǔ)的 int 值就是兩個(gè)索引的乘積。也就是說(shuō),products[2][4] 的值是 8,products[3][7] 的值是 21。

創(chuàng)建多維數(shù)組要使用 new 關(guān)鍵字,而且要指定每個(gè)維度中數(shù)組的大小。例如:

int[][] products = new int[10][10];

在某些語(yǔ)言中,會(huì)把這樣的數(shù)組創(chuàng)建成包含 100 個(gè) int 值的數(shù)組,但 Java 不會(huì)這樣處理。這行代碼會(huì)做三件事。

1? ? 聲明一個(gè)名為 products 的變量,保存一個(gè)由 int[] 類型數(shù)組組成的數(shù)組。

2? ? 創(chuàng)建一個(gè)有 10 個(gè)元素的數(shù)組,保存 10 個(gè) int[] 類型的數(shù)組。

3? ? 再創(chuàng)建 10 個(gè)數(shù)組,每個(gè)都由 10 個(gè) int 類型的元素組成。然后把這 10 個(gè)新數(shù)組指定為

前一步創(chuàng)建的數(shù)組的元素。這 10 個(gè)新數(shù)組中的每一個(gè) int 類型元素的默認(rèn)值都是 0。換種方式說(shuō),前面的單行代碼等效于下述代碼:

int[][] products = new int[10][]; // 保存10個(gè)int[]類型值的數(shù)組

for(int i = 0; i < 10; i++) // 循環(huán)10次......

????????????????products[i] = new int[10]; // ......創(chuàng)建10個(gè)數(shù)組

new 關(guān)鍵字會(huì)自動(dòng)執(zhí)行這些額外的初始化操作。超過(guò)兩個(gè)維度的數(shù)組也是一樣:

float[][][] globalTemperatureData = new float[360][180][100];

使用 new 關(guān)鍵字創(chuàng)建多維數(shù)組時(shí),無(wú)需指定所有維度的大小,只要為最左邊的幾個(gè)維度指定大小就行。例如,下面兩行代碼都是合法的:

float[][][] globalTemperatureData = new float[360][][];

float[][][] globalTemperatureData = new float[360][180][];

第一行代碼創(chuàng)建一個(gè)一維數(shù)組,元素是 float[][] 類型。第二行代碼創(chuàng)建一個(gè)二維數(shù)組,元素是 float[] 類型。不過(guò),如果只為數(shù)組的部分維度指定大小,這些維度必須位于最左邊。下述代碼是不合法的:

float[][][] globalTemperatureData = new float[360][][100]; // 錯(cuò)誤!

float[][][] globalTemperatureData = new float[][180][100]; // 錯(cuò)誤!

和一維數(shù)組一樣,多維數(shù)組也能使用數(shù)組初始化程序初始化,使用嵌套的花括號(hào)把數(shù)組嵌套在數(shù)組中即可。例如,可以像下面這樣聲明、創(chuàng)建并初始化一個(gè) 5×5 乘法表:

int[][] products = { {0, 0, 0, 0, 0},

? ? ? ? ? ? ? ? ? ? ? ? ? ?{0, 1, 2, 3, 4},

? ? ? ? ? ? ? ? ? ? ? ? ? ?{0, 2, 4, 6, 8},

? ? ? ? ? ? ? ? ? ? ? ? ? {0, 3, 6, 9, 12},

? ? ? ? ? ? ? ? ? ? ? ? ? {0, 4, 8, 12, 16} };

如果不想聲明變量就使用多維數(shù)組,可以使用匿名初始化程序句法:

boolean response = bilingualQuestion(question, new String[][] {

????????????????????????????????????????????????????????????????????????????????{ "Yes", "No" },

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? { "Oui", "Non" }});

使用 new 關(guān)鍵字創(chuàng)建多維數(shù)組時(shí),往往最好只使用矩形數(shù)組,即每個(gè)維度的數(shù)組大小相同。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容