Java 陣列使用攻略:原理、語法、實務與雷點全整理【Thinking in Java筆記(4-5)】
如果你是 Java 新手,那「陣列」這個詞可能就像冰箱裡的透明盒子一樣:看得到、摸不到,更不知道裡面要放什麼。簡單來說,陣列是一種用來「一次裝很多東西」的容器。你可以想成是成績表、購物清單,或是一排櫃子,每格都可以放一筆資料
陣列是寫程式的必備工具,但初學者常常搞混怎麼宣告、怎麼初始化,甚至會搞出一些神秘的錯誤像是 NullPointerException
這篇文章就要幫你從零開始搞懂陣列這種東西,確保你下次看到它的時候不會想要格式化電腦
所以這篇文章就是來幫你:
- 解釋 Java 陣列怎麼宣告、初始化、動態建立
- 用具體的生活比喻解釋每個概念
- 示範怎麼在方法之間丟來丟去不爆炸
陣列的宣告方式
你想像一下:你要幫全班同學做一份成績表,那你會需要一張表格對吧?陣列就是程式裡的表格。你要先說明你要放「什麼類型的資料」,再來才是放在哪裡
int[] scores;
這是說:「我要一張放整數的表格。」方括號放在型別後或變數後面都行:
int scores[];
但注意:這裡你只是告訴 Java:「我要一份能裝整數的表格」,但你還沒跟它說這表格到底要多大。換句話說,這只是建立一個『參考』,就像你在地圖上圈出要蓋房子的地點,還沒真正動工
int scores[5]; // ❌ 不合法
Java 編譯器會報錯。因為 Java 要求你在建立實體時,用 new
關鍵字來分配記憶體空間。否則你只是說我要這種東西,卻沒真正提供它可以住的地方
陣列初始化方式
你可以想像初始化陣列,就像設計一張成績表。當你聲明陣列(像 int[] scores;
),就像你在白紙上畫了一個表格的大致輪廓,決定這張表要記錄的是分數(整數),但你還沒決定這張表有幾格、也沒真的印出來
初始化,就是你真的去印出一張有固定格數的表格,準備拿來填資料。如果你沒做這步,程式執行時就會發現:「你說要用表格,但現在一張都沒有!」這時你還不能對這張表格做任何操作,因為它根本就還不存在
所以記住:宣告只是說「我要這個東西」,初始化才是「真的給我這個東西,而且我準備好了要用了」
有兩種常見方式可以初始化陣列:
- 直接指定內容(匿名陣列):
int[] scores = {90, 80, 70};
這個寫法表示你不只說「我要這樣的表格」,你還直接把三筆資料塞進去了。Java 會根據你填的東西自動幫你決定陣列的大小,這就像你印了一份已經填好三格內容的表格
- 使用
new
分配記憶體空間:
int[] scores = new int[3];
這代表你說:「我要三格的空白表格。」此時內容會自動初始化為 0,就像你印了一份三格但還沒填資料的表格。你可以之後用迴圈一格格填進去
不初始化的話,就只是許下一個承諾而已,等你真的要用資料時,很可能會遇到錯誤(像是 null 或 0 沒搞清楚哪一個),所以一開始就把事情做清楚,對你有好處
陣列的參考與修改(⚠️ 陷阱多)
我們在初始化方式中提過,當你用 new
關鍵字建立陣列,其實是產生了一個在記憶體中的「表格」,並回傳一個參考給變數
但要特別注意:Java 中的陣列是參考型別(reference type),這意味著當你將一個陣列變數指派給另一個陣列變數時,兩者其實指向的是同一塊記憶體位置,也就是同一份陣列資料
這就像是你原本有一份共用的 Google Sheet,然後把連結分享給別人。你們看到的是同一份表格,只是從不同帳號登入。你改了,他也會看到改過的資料
如果你不清楚這一點,就會像下面這段程式碼一樣,踩到坑:
public class ArraysOfPrimitives {
public static void main(String[] args) {
int[] a1 = { 1, 2, 3, 4, 5 };
int[] a2;
a2 = a1;
for(int i = 0; i < a2.length; i++)
a2[i] = a2[i] + 1;
for(int i = 0; i < a1.length; i++)
System.out.println("a1[" + i + "] = " + a1[i]);
}
} /* Output:
a1[0] = 2
a1[1] = 3
a1[2] = 4
a1[3] = 5
a1[4] = 6
*/
你以為你改的是 a2,其實 a1 也變了。因為他們指向的是同一張表格,不是複製品
提示:所有陣列都有一個內建的屬性length
,可以用來獲取陣列的長度,但不能修改它。陣列的索引從 0 開始,最大索引為length - 1
。如果超出這個範圍,就會發生執行時錯誤,通常會看到像ArrayIndexOutOfBoundsException
這種訊息。這是 Java 在保護你,不讓你存取不屬於陣列的空間
動態建立陣列(你不知道會有幾格)
有時候你寫程式時並不知道一開始需要多大的陣列,這種情況就會用到「動態建立陣列」。這就像你要辦活動,開放現場報名,但你事先不知道到底會來幾個人。你得等人真的出現後,根據人數決定要印幾張報到表
在 Java 裡,你可以使用 new
搭配變數來動態決定陣列的大小: new
來動態建立:
public class ArrayNew {
public static void main(String[] args) {
int[] a;
Random rand = new Random();
a = new int[rand.nextInt(20)];
System.out.println("length of a = " + a.length);
System.out.println(Arrays.toString(a));
}
} /* Output:
length of a = 18
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
*/
這裡你就像是在說:「我不知道會來幾個人,但幫我預留一排椅子,數量等我骰個骰子決定。」這就是 rand.nextInt(20)
的用途——它會給你一個 0 到 19 的隨機整數,Java 就會根據這個數字幫你開出對應大小的陣列
但這時你還沒填入任何真正的資料。因為這是基本型態(int)陣列,Java 就會貼心地幫你把每個格子都預設成 0。這有點像是你先把椅子排好了,每張椅子上先放一張紙條寫著「空位中」
物件陣列的陷阱(Integer vs int)
現在你換成了 Integer:
Integer[] objs = new Integer[rand.nextInt(10)];
你可能期待它像 int[]
陣列一樣,預設值通通是 0。但實際上,你印出來會看到:
[null, null, null...]
這不是 bug,而是 Java 的設計。因為 Integer 是一種物件型態,不是基本型態,所以在建立這類陣列時,每一格的預設值是 null
,也就是「什麼都還沒放進去」的意思
想像你有一張空白表格,每一格代表一位學生的報名資料。基本型態(像 int
)的表格會自動填上「0」,像是預設說「還沒填分數」,但物件型態的表格就像每一格上面只貼了一張紙條寫「這裡應該放一個人,但目前沒人來」
如果你直接使用這些 null
,例如對它們呼叫方法,就會出現 NullPointerException,所以你必須先一格一格地「把人填進去」
public class ArrayClassObj {
public static void main(String[] args) {
Random rand = new Random(47);
Integer[] a = new Integer[rand.nextInt(20)];
System.out.println("length of a = " + a.length);
for(int i = 0; i < a.length; i++)
a[i] = rand.nextInt(500); // Autoboxing
System.out.println(Arrays.toString(a));
}
} /* Output: (Sample)
length of a = 18
[55, 193, 361, 461, 429, 368, 200, 22, 207, 288, 128, 51, 89, 309, 278, 498, 361, 20]
*/
這裡我們使用了 Java 的 autoboxing 功能,它會自動把 int
轉成 Integer
,幫你省掉 new Integer(...)
這種老派寫法。總之,記得:物件陣列預設是 null
,不自己動手填好是不會動的
陣列初始化的另一種方式(多一點語法糖)
除了前面介紹的使用 new
關鍵字來明確分配陣列大小外,Java 也提供了一些語法更簡潔、方便閱讀的初始化方式,特別適合在你一開始就知道要放哪些資料時使用。這種語法通常被稱為語法糖(syntax sugar),因為它讓寫起來更輕鬆、讀起來更順眼
你可以直接用大括號 {}
搭配一串值,Java 會自動判斷大小並幫你產生一個對應大小的陣列物件。例如:
public class ArrayInit {
public static void main(String[] args) {
Integer[] a = {
new Integer(1),
new Integer(2),
3, // Autoboxing
};
Integer[] b = new Integer[]{
new Integer(1),
new Integer(2),
3, // Autoboxing
};
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));
}
} /* Output:
[1, 2, 3]
[1, 2, 3]
*/
兩者結果都一樣。這裡 3
是一個 int
,但因為你要放進 Integer[]
這個物件型態的陣列,Java 會自動幫你把 3
包裝成 Integer.valueOf(3)
,這個過程稱為自動裝箱(autoboxing)
你可以把這個過程想像成:你本來是直接把硬幣(int)丟進抽屜(陣列),但現在規定每個硬幣都要裝進一個透明小盒子(Integer 物件)才能放進去。Java 幫你完成這個包裝,讓你不需要手動寫 new Integer(3)
,而是直接寫 3
,省時省心。
這讓程式碼更簡潔,也更容易閱讀,特別是在初始化時快速塞值的時候
列表最後的逗號是可以有的,不用強迫症地刪掉它
使用陣列傳遞參數給方法
想像你要請別人幫你處理一整排的報名表,那你就要把這一排資料打包成陣列傳給他。陣列在這裡扮演的角色就像是一個資料打包箱,可以讓你一次把很多資料(不管是整數、字串、還是物件)統一遞交給某個方法進行處理
這種做法讓方法更靈活,因為你不用每次都寫死只能接受幾個參數,也不用每次都寫一樣的邏輯來處理單個資料。用陣列,你可以傳 3 筆也可以傳 300 筆,方法都能用一樣的方式來處理
最常見的例子就是 main(String[] args)
,這就是 Java 的入口方法,它接收的是一個字串陣列,可以讓使用者從 command line 輸入任意多個參數
陣列可以作為方法的參數,允許在方法之間傳遞多個資料。這在需要動態創建並初始化陣列時特別有用,例如將字串陣列作為參數傳遞給其他方法
例如你寫了一個程式叫做 Greet.java
,在命令列輸入:
java Greet Alice Bob Charlie
那麼 args
這個字串陣列就會自動收到:
{"Alice", "Bob", "Charlie"}
你可以在程式裡用 args[0]
拿到 "Alice",用 args.length
知道總共有幾個名字。
這種設計讓使用者能靈活地傳入不同數量的資料,不用每次寫死,只要透過陣列接收就能處理彈性參數數量
以下是一個直接對另一個方法傳遞整個陣列的範例:
public class DynamicArray {
public static void main(String[] args) {
Other.main(new String[]{ "fiddle", "de", "dum" });
}
}
class Other {
public static void main(String[] args) {
for(String s : args)
System.out.print(s + " ");
}
} /* Output:
fiddle de dum
*/
這個範例中,我們在 DynamicArray
類別的 main
方法中,透過 new String[]{"fiddle", "de", "dum"}
建立了一個字串陣列,並把它當作參數傳給 Other.main()
在 Other
類別的 main
方法中,透過增強型 for 迴圈 for (String s : args)
將接收到的字串陣列一一印出。這清楚示範了陣列如何作為方法參數被傳遞與使用
你可以想像這個流程就像把一疊報名資料遞給另一個人(方法),他拿到後照順序讀出來
總結:這些你真的該記住的事
到這裡為止,你應該已經對 Java 陣列有個比較完整的概念。總結一下,我們一路從宣告講到初始化,從基本型態講到物件型態,還帶你看了動態大小、方法參數傳遞、autoboxing 的小技巧。這邊快速幫你整理幾個你真的應該要記住的觀念:
- 陣列大小是固定的:你一開始說幾格,就只能用幾格。想改大小?很抱歉,請新開一個,自己搬家
- 宣告不等於初始化:你說「我要一份整數表格」只是宣告。你還得用
new
才會真的生出那份表格 - 基本型態陣列預設值是 0,物件型態預設是 null:這差很多。物件的 null 如果你不初始化還拿來用,程式會爆炸給你看
- 陣列是參考型別:兩個變數指到同一個陣列的話,改一個,另一個也會變。就像你們在改同一份 Google Sheet,誰動都會同步更新
length
是你的好朋友:用它控制迴圈,不用擔心少跑或超出界。但記住它是唯讀,你不能改它- 陣列可以當作方法參數傳來傳去:很適合處理多筆資料,也讓方法設計更彈性,不用寫死接受幾個變數
理解這些基本概念,你就能用陣列處理大多數入門需求了。這是邁向 Java 中階的地基,蓋房子要打好地基你知道吧?
Comments ()