Java中初始化數(shù)組
數(shù)組是一種有用的數(shù)據(jù)類型,用于管理在連續(xù)內(nèi)存位置中建模最好的集合元素。下面是如何有效地使用它們。
有使用 C 或者 FORTRAN 語言編程經(jīng)驗的人會對數(shù)組的概念很熟悉。它們基本上是一個連續(xù)的內(nèi)存塊,其中每個位置都是某種數(shù)據(jù)類型:整型、浮點型或者諸如此類的數(shù)據(jù)類型。
Java 的情況與此類似,但是有一些額外的問題。
一個數(shù)組的示例
讓我們在 Java 中創(chuàng)建一個長度為 10 的整型數(shù)組:
int[] ia = new int[10];
上面的代碼片段會發(fā)生什么?從左到右依次是:
- 最左邊的
int[]將變量的類型聲明為int數(shù)組(由[]表示)。 - 它的右邊是變量的名稱,當前為
ia。 - 接下來,
=告訴我們,左側(cè)定義的變量賦值為右側(cè)的內(nèi)容。 - 在
=的右側(cè),我們看到了new,它在 Java 中表示一個對象正在被初始化中,這意味著已為其分配存儲空間并調(diào)用了其構(gòu)造函數(shù)(請參見此處以獲取更多信息)。 - 然后,我們看到
int[10],它告訴我們正在初始化的這個對象是包含 10 個整型的數(shù)組。
因為 Java 是強類型的,所以變量 ia 的類型必須跟 = 右側(cè)表達式的類型兼容。
初始化示例數(shù)組
讓我們把這個簡單的數(shù)組放在一段代碼中,并嘗試運行一下。將以下內(nèi)容保存到一個名為 Test1.java 的文件中,使用 javac 編譯,使用 java 運行(當然是在終端中):
import java.lang.*;public class Test1 {public static void main(String[] args) {int[] ia = new int[10]; // 見下文注 1System.out.println("ia is " + ia.getClass()); // 見下文注 2for (int i = 0; i < ia.length; i++) // 見下文注 3System.out.println("ia[" + i + "] = " + ia[i]); // 見下文注 4}}
讓我們來看看最重要的部分。
- 我們聲明和初始化了長度為 10 的整型數(shù)組,即
ia,這顯而易見。 - 在下面的行中,我們看到表達式
ia.getClass()。沒錯,ia是屬于一個類的對象,這行代碼將告訴我們是哪個類。 - 在緊接的下一行中,我們看到了一個循環(huán)
for (int i = 0; i < ia.length; i++),它定義了一個循環(huán)索引變量i,該變量遍歷了從 0 到比ia.length小 1 的序列,這個表達式告訴我們在數(shù)組ia中定義了多少個元素。 - 接下來,循環(huán)體打印出
ia的每個元素的值。
當這個程序編譯和運行時,它產(chǎn)生以下結(jié)果:
me@mydesktop:~/Java$ javac Test1.javame@mydesktop:~/Java$ java Test1ia is class [Iia[0] = 0ia[1] = 0ia[2] = 0ia[3] = 0ia[4] = 0ia[5] = 0ia[6] = 0ia[7] = 0ia[8] = 0ia[9] = 0me@mydesktop:~/Java$
ia.getClass() 的輸出的字符串表示形式是 [I,它是“整數(shù)數(shù)組”的簡寫。與 C 語言類似,Java 數(shù)組以第 0 個元素開始,擴展到第 <數(shù)組大小> - 1 個元素。如上所見,我們可以看到數(shù)組 ia 的每個元素都(似乎由數(shù)組構(gòu)造函數(shù))設置為零。
所以,就這些嗎?聲明類型,使用適當?shù)某跏蓟?,就完成了?
好吧,并沒有。在 Java 中有許多其它方法來初始化數(shù)組。
為什么我要初始化一個數(shù)組,有其它方式嗎?
像所有好的問題一樣,這個問題的答案是“視情況而定”。在這種情況下,答案取決于初始化后我們希望對數(shù)組做什么。
在某些情況下,數(shù)組自然會作為一種累加器出現(xiàn)。例如,假設我們正在編程實現(xiàn)計算小型辦公室中一組電話分機接收和撥打的電話數(shù)量。一共有 8 個分機,編號為 1 到 8,加上話務員的分機,編號為 0。 因此,我們可以聲明兩個數(shù)組:
int[] callsMade;int[] callsReceived;
然后,每當我們開始一個新的累計呼叫統(tǒng)計數(shù)據(jù)的周期時,我們就將每個數(shù)組初始化為:
callsMade = new int[9];callsReceived = new int[9];
在每個累計通話統(tǒng)計數(shù)據(jù)的最后階段,我們可以打印出統(tǒng)計數(shù)據(jù)。粗略地說,我們可能會看到:
import java.lang.*;import java.io.*;public class Test2 {public static void main(String[] args) {int[] callsMade;int[] callsReceived;// 初始化呼叫計數(shù)器callsMade = new int[9];callsReceived = new int[9];// 處理呼叫……// 分機撥打電話:callsMade[ext]++// 分機接聽電話:callsReceived[ext]++// 匯總通話統(tǒng)計System.out.printf("%3s%25s%25s\n", "ext", " calls made","calls received");for (int ext = 0; ext < callsMade.length; ext++) {System.out.printf("%3d%25d%25d\n", ext,callsMade[ext], callsReceived[ext]);}}}
這會產(chǎn)生這樣的輸出:
me@mydesktop:~/Java$ javac Test2.javame@mydesktop:~/Java$ java Test2ext calls made calls received0 0 01 0 02 0 03 0 04 0 05 0 06 0 07 0 08 0 0me@mydesktop:~/Java$
看來這一天呼叫中心不是很忙。
在上面的累加器示例中,我們看到由數(shù)組初始化程序設置的零起始值可以滿足我們的需求。但是在其它情況下,這個起始值可能不是正確的選擇。
例如,在某些幾何計算中,我們可能需要將二維數(shù)組初始化為單位矩陣(除沿主對角線———左上角到右下角——以外所有全是零)。我們可以選擇這樣做:
double[][] m = new double[3][3];for (int d = 0; d < 3; d++) {m[d][d] = 1.0;}
在這種情況下,我們依靠數(shù)組初始化器 new double[3][3] 將數(shù)組設置為零,然后使用循環(huán)將主對角線上的元素設置為 1。在這種簡單情況下,我們可以使用 Java 提供的快捷方式:
double[][] m = {{1.0, 0.0, 0.0},{0.0, 1.0, 0.0},{0.0, 0.0, 1.0}};
這種可視結(jié)構(gòu)特別適用于這種應用程序,在這種應用程序中,它便于復查數(shù)組的實際布局。但是在這種情況下,行數(shù)和列數(shù)只在運行時確定時,我們可能會看到這樣的東西:
int nrc;// 一些代碼確定行數(shù)和列數(shù) = nrcdouble[][] m = new double[nrc][nrc];for (int d = 0; d < nrc; d++) {m[d][d] = 1.0;}
值得一提的是,Java 中的二維數(shù)組實際上是數(shù)組的數(shù)組,沒有什么能阻止無畏的程序員讓這些第二層數(shù)組中的每個數(shù)組的長度都不同。也就是說,下面這樣的事情是完全合法的:
int [][] differentLengthRows = {{1, 2, 3, 4, 5},{6, 7, 8, 9},{10, 11, 12},{13, 14},{15}};
在涉及不規(guī)則形狀矩陣的各種線性代數(shù)應用中,可以應用這種類型的結(jié)構(gòu)(有關(guān)更多信息,請參見此 Wikipedia 文章)。除此之外,既然我們了解到二維數(shù)組實際上是數(shù)組的數(shù)組,那么以下內(nèi)容也就不足為奇了:
differentLengthRows.length
可以告訴我們二維數(shù)組 differentLengthRows 的行數(shù),并且:
differentLengthRows[i].length
告訴我們 differentLengthRows 第 i 行的列數(shù)。
深入理解數(shù)組
考慮到在運行時確定數(shù)組大小的想法,我們看到數(shù)組在實例化之前仍需要我們知道該大小。但是,如果在處理完所有數(shù)據(jù)之前我們不知道大小怎么辦?這是否意味著我們必須先處理一次以找出數(shù)組的大小,然后再次處理?這可能很難做到,尤其是如果我們只有一次機會使用數(shù)據(jù)時。
Java 集合框架很好地解決了這個問題。提供的其中一項是 ArrayList 類,它類似于數(shù)組,但可以動態(tài)擴展。為了演示 ArrayList 的工作原理,讓我們創(chuàng)建一個 ArrayList 對象并將其初始化為前 20 個斐波那契數(shù)字:
import java.lang.*;import java.util.*;public class Test3 {public static void main(String[] args) {ArrayList<Integer> fibos = new ArrayList<Integer>();fibos.add(0);fibos.add(1);for (int i = 2; i < 20; i++) {fibos.add(fibos.get(i - 1) + fibos.get(i - 2));}for (int i = 0; i < fibos.size(); i++) {System.out.println("fibonacci " + i + " = " + fibos.get(i));}}}
上面的代碼中,我們看到:
- 用于存儲多個
Integer的ArrayList的聲明和實例化。 - 使用
add()附加到ArrayList實例。 - 使用
get()通過索引號檢索元素。 - 使用
size()來確定ArrayList實例中已經(jīng)有多少個元素。
這里沒有展示 put() 方法,它的作用是將一個值放在給定的索引號上。
該程序的輸出為:
fibonacci 0 = 0fibonacci 1 = 1fibonacci 2 = 1fibonacci 3 = 2fibonacci 4 = 3fibonacci 5 = 5fibonacci 6 = 8fibonacci 7 = 13fibonacci 8 = 21fibonacci 9 = 34fibonacci 10 = 55fibonacci 11 = 89fibonacci 12 = 144fibonacci 13 = 233fibonacci 14 = 377fibonacci 15 = 610fibonacci 16 = 987fibonacci 17 = 1597fibonacci 18 = 2584fibonacci 19 = 4181
ArrayList 實例也可以通過其它方式初始化。例如,可以給 ArrayList 構(gòu)造器提供一個數(shù)組,或者在編譯過程中知道初始元素時也可以使用 List.of() 和 array.aslist() 方法。我發(fā)現(xiàn)自己并不經(jīng)常使用這些方式,因為我對 ArrayList 的主要用途是當我只想讀取一次數(shù)據(jù)時。
此外,對于那些喜歡在加載數(shù)據(jù)后使用數(shù)組的人,可以使用 ArrayList 的 toArray() 方法將其實例轉(zhuǎn)換為數(shù)組;或者,在初始化 ArrayList 實例之后,返回到當前數(shù)組本身。
Java 集合框架提供了另一種類似數(shù)組的數(shù)據(jù)結(jié)構(gòu),稱為 Map(映射)。我所說的“類似數(shù)組”是指 Map 定義了一個對象集合,它的值可以通過一個鍵來設置或檢索,但與數(shù)組(或 ArrayList)不同,這個鍵不需要是整型數(shù);它可以是 String 或任何其它復雜對象。
例如,我們可以創(chuàng)建一個 Map,其鍵為 String,其值為 Integer 類型,如下:
Map<String, Integer> stoi = new Map<String, Integer>();
然后我們可以對這個 Map 進行如下初始化:
stoi.set("one",1);stoi.set("two",2);stoi.set("three",3);
等類似操作。稍后,當我們想要知道 "three" 的數(shù)值時,我們可以通過下面的方式將其檢索出來:
stoi.get("three");
在我的認知中,Map 對于將第三方數(shù)據(jù)集中出現(xiàn)的字符串轉(zhuǎn)換為我的數(shù)據(jù)集中的一致代碼值非常有用。作為數(shù)據(jù)轉(zhuǎn)換管道的一部分,我經(jīng)常會構(gòu)建一個小型的獨立程序,用作在處理數(shù)據(jù)之前清理數(shù)據(jù);為此,我?guī)缀蹩偸菚褂靡粋€或多個 Map。
值得一提的是,ArrayList 的 ArrayList 和 Map 的 Map 是很可能的,有時也是合理的。例如,假設我們在看樹,我們對按樹種和年齡范圍累計樹的數(shù)目感興趣。假設年齡范圍定義是一組字符串值(“young”、“mid”、“mature” 和 “old”),物種是 “Douglas fir”、“western red cedar” 等字符串值,那么我們可以將這個 Map 中的 Map 定義為:
Map<String, Map<String, Integer>> counter = new Map<String, Map<String, Integer>>();
這里需要注意的一件事是,以上內(nèi)容僅為 Map 的行創(chuàng)建存儲。因此,我們的累加代碼可能類似于:
// 假設我們已經(jīng)知道了物種和年齡范圍if (!counter.containsKey(species)) {counter.put(species,new Map<String, Integer>());}if (!counter.get(species).containsKey(ageRange)) {counter.get(species).put(ageRange,0);}
此時,我們可以這樣開始累加:
counter.get(species).put(ageRange, counter.get(species).get(ageRange) + 1);
最后,值得一提的是(Java 8 中的新特性)Streams 還可以用來初始化數(shù)組、ArrayList 實例和 Map 實例。關(guān)于此特性的詳細討論可以在此處和此處中找到。




















