CodeGym /課程 /JAVA 25 SELF /一維陣列的基本操作

一維陣列的基本操作

JAVA 25 SELF
等級 7 , 課堂 2
開放

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 設計了特殊常數 —— nullnull 是空參考。 當為物件型別建立變數時,它的起始值是 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}

只有基本型別才有「預設值」。其他型別預設是 nullString 的起始值不是空字串。人生就是如此。

2. 記憶體中的 String 陣列

圖 1. String 物件在記憶體中的佈局:

記憶體中的 String 陣列

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

圖 2. 整數陣列在記憶體中的佈局:

記憶體中的 int 陣列

這張圖你也看過了。

圖 3. 字串陣列在記憶體中的佈局:

String 陣列在記憶體中的佈局

左邊是 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-each 迴圈轉換為下面的程式碼:

for (int i = 0; i < scores.length; i++)
{
    int score = scores[i];
    System.out.println("分數: " + score);
}
for-each 在「底層」如何運作

所以沒有任何魔法。也因此就能理解,為什麼你不能透過變數 score 來修改陣列元素。

再寫個例子:計算所有分數的總和。

int sum = 0;
for (int score : scores)
{
    sum += score;
}
System.out.println("所有分數之和: " + sum);
透過 for-each 計算陣列值的總和

什麼時候用哪種迴圈?

  • 如果 需要索引 或需要 修改元素 —— 用 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。
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION