1. 陣列類型 String
簡單介紹一下 String 類型的陣列。
如前所述,陣列可以是任意類型。因此可以建立 String 類型的陣列。以下展示如果我們需要撰寫一個「從鍵盤輸入 10 行並倒序輸出到螢幕」的程式,程式碼會長什麼樣子。
String[] array = new String[10]; // 建立一個包含 10 個元素的陣列物件
for (int i = 0; i < 10; i++) // 循環從 0 到 9
{
array[i] = console.nextLine(); // 從鍵盤讀取一行並存入陣列的對應位置
}
for (int i = 9; i >= 0; i--) // 循環從 9 到 0
{
System.out.println(array[i]); // 將當前陣列元素輸出到螢幕
}
程式幾乎沒有變!只是在建立陣列時把 int 類型換成了 String。其餘完全一樣!
認識 null
來個小問題——這些陣列在剛建立之後,各自的每個元素裡面是什麼?
int[] numbers = new int[10];
String[] strings = new String[10];
User[] users = new User[10];
想一想,很可能 numbers 的元素是 0。正確。
照這個邏輯,strings 的元素應該是空字串 —— ""。姑且算對。
那 users 陣列的元素裡是什麼?空的 User 嗎?
這裡有個陷阱:並不是每種資料型別都有「預設值」。因此 Java 設計了特殊常數 —— null。null 是空參考。 當為物件型別建立變數時,它的起始值是 null:不指向任何東西、沒有參考目標。
String name; // name 目前是 null
name = "Alex"; // name 參考到字串物件 "Alex"
name = null; // name 變回 null
處理 null
當參考是 null 時,不能呼叫物件的方法 —— 因為根本沒有物件!以下程式會以錯誤結束:
String name; // name 目前是 null
String upperName = name.toUpperCase(); // 會拋出錯誤 NullPointerException:name == null
回到剛才的「小問題」,答案如下:
int[] numbers = new int[10]; // {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
String[] strings = new String[10]; // {null, null, null, null, null, null, null, null, null, null}
User[] users = new User[10]; // {null, null, null, null, null, null, null, null, null, null}
只有基本型別才有「預設值」。其他型別預設是 null。String 的起始值不是空字串。人生就是如此。
2. 記憶體中的 String 陣列
圖 1. String 物件在記憶體中的佈局:

請注意,字串的文字不直接存放在變數裡:它會被分配到另一塊記憶體。而在 String 類型的變數中,存放的是指向文字物件的位址(參考)。
圖 2. 整數陣列在記憶體中的佈局:

這張圖你也看過了。
圖 3. 字串陣列在記憶體中的佈局:

左邊是 String[] 類型的陣列變數(存的是陣列物件的位址)。
中間是 String 類型的陣列物件。
右邊是實際的字串物件,裡面保存各種文字。
在 String 類型的陣列物件的每個元素中,存的不是字串本身(文字),而是它們的位址(參考)。就像在 String 類型的變數裡存的是字串(文字)的位址一樣。
3. Java 中的陣列快速初始化
陣列非常好用,因此 Java 的設計者盡力讓操作更便利。首先,他們 簡化了陣列的初始化 —— 設定它的起始值。
很多時候,除了程式從外部讀取的資料之外,還需要一些內部資料。例如,我們要在陣列中保存每個月的天數。程式碼可以這樣寫:
int[] months = new int[12];
months[0] = 31; // 一月
months[1] = 28; // 二月
months[2] = 31; // 三月
months[3] = 30; // 四月
months[4] = 31; // 五月
months[5] = 30; // 六月
months[6] = 31; // 七月
months[7] = 31; // 八月
months[8] = 30; // 九月
months[9] = 31; // 十月
months[10] = 30; // 十一月
months[11] = 31; // 十二月
不過有更簡潔的寫法 —— 感謝 Java 的設計者:
// 各月份的天數
int[] months = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
你可以直接用逗號列出所有值!
編譯器可以根據陣列變數的型別來推斷容器(陣列物件)的型別;而陣列長度則可以直接統計大括號中的元素個數。
因此可以再更短:
// 各月份的天數
int[] months = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
是不是很優雅?🙂 這種寫法稱為「快速初始化陣列」。而且不只適用於 int 類型...
// 月份名稱
String[] months = { "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月" };
4. 迴圈 for-each
遍歷陣列的所有元素是如此常見,以至於為它 設計了專用的迴圈 —— for-each。寫法如下:
for (int score : scores)
{
System.out.println("分數: " + score);
}
這裡迴圈會自動遍歷元素——不需要手動索引。但 不能直接修改元素(迴圈變數只保存值的副本),不過用來單純「走訪」陣列非常方便。
編譯器會把 for-each 迴圈轉換為下面的程式碼:
for (int i = 0; i < scores.length; i++)
{
int score = scores[i];
System.out.println("分數: " + score);
}
所以沒有任何魔法。也因此就能理解,為什麼你不能透過變數 score 來修改陣列元素。
再寫個例子:計算所有分數的總和。
int sum = 0;
for (int score : scores)
{
sum += score;
}
System.out.println("所有分數之和: " + sum);
什麼時候用哪種迴圈?
- 如果 需要索引 或需要 修改元素 —— 用 for 迴圈。
- 如果只是遍歷值 —— for-each 更快也更乾淨。
5. 修改陣列元素
如果要把每個成績增加 1 分(例如表現良好加分),就需要帶索引的經典 for,因為只有這樣才能修改陣列中的值。
範例:每個元素加 1
for (int i = 0; i < grades.length; i++) {
grades[i] = grades[i] + 1;
}
執行這段程式碼後,grades 陣列會有新的值。
為什麼不能用 for-each 這樣做?
for (int grade : grades) {
grade = grade + 1; // 無效!
}
這段程式碼不會改變陣列,因為 grade 是值的 副本,不是對元素本身的參考。
6. 基於陣列的計算
陣列常用於計算:總和、平均值、最大值、最小值等等。
範例:陣列所有元素的總和
int sum = 0;
for (int i = 0; i < grades.length; i++) {
sum += grades[i]; // 等同於 sum = sum + grades[i];
}
System.out.println("分數總和: " + sum);
範例:尋找最大值
int max = grades[0]; // 從第一個元素開始
for (int i = 1; i < grades.length; i++) {
if (grades[i] > max) {
max = grades[i];
}
}
System.out.println("最高分: " + max);
範例:尋找最小值
int min = grades[0]; // 從第一個元素開始
for (int i = 1; i < grades.length; i++) {
if (grades[i] < min) {
min = grades[i];
}
}
System.out.println("最低分: " + min);
範例:計算平均值
int sum = 0;
for (int i = 0; i < grades.length; i++) {
sum += grades[i];
}
double average = (double) sum / grades.length; // 一定要轉型為 double!
System.out.println("平均分: " + average);
請注意:為了得到小數而非整數,需要在除法前先把總和顯式轉成 double。
7. 實作練習
從鍵盤輸入陣列
常常需要用使用者輸入來填充陣列。為此可搭配 Scanner 和迴圈。
Scanner console = new Scanner(System.in);
int n = 5; // 陣列大小
int[] numbers = new int[n];
System.out.println("請輸入 " + n + " 個數字:");
for (int i = 0; i < n; i++) {
numbers[i] = console.nextInt();
}
System.out.println("你輸入的是:");
for (int i = 0; i < n; i++) {
System.out.println(numbers[i]);
}
倒序輸出陣列
有時需要從尾部開始輸出(例如想知道誰最後進了房間)。
for (int i = grades.length - 1; i >= 0; i--) {
System.out.println("第 " + (i + 1) + " 筆成績: " + grades[i]);
}
8. 操作一維陣列的常見錯誤
錯誤 1:陣列索引越界
最常見的問題是嘗試存取不存在的元素。在 Java 會拋出例外 ArrayIndexOutOfBoundsException。記住:最後一個有效索引是 arr.length - 1。
int[] arr = new int[5];
System.out.println(arr[5]); // 錯誤!索引只能是 0 到 4。
錯誤 2:忘了初始化陣列
只宣告了變數,卻沒建立陣列:
int[] arr;
arr[0] = 5; // 錯誤!陣列尚未建立。
必須使用 new 建立陣列:
arr = new int[10];
錯誤 3:試圖用 for-each 修改陣列元素
在 for-each 迴圈中,變數是值的副本,而不是元素的參考:
for (int x : arr) {
x = 100; // 不會修改陣列!
}
若要修改值,請使用帶索引的傳統 for。
錯誤 4:錯用陣列的長度
有時會把陣列長度和最後一個索引混淆:
for (int i = 0; i <= arr.length; i++) { // 錯誤!應該寫成 i < arr.length
// ...
}
這段程式碼會導致越界。
錯誤 5:隱式型別轉換
如果陣列類型是 int,不能直接賦值為 double:
int[] arr = new int[3];
arr[0] = 3.14; // 錯誤!3.14 是 double。
GO TO FULL VERSION