數(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ù)組大小相同祠墅。