Java List 完全攻略:從零開始掌握 ArrayList, LinkedList 用法與實戰技巧

1. 前言

Java 中 List 的重要性是什麼?

在 Java 程式設計中,「List」是經常出現的資料結構。特別是在需要一次管理多個值的情況下,由於它比陣列更靈活且易於使用,因此在許多實際應用中受到青睞。

「List」是 Java 集合框架的核心介面,通過 ArrayList、LinkedList 等各種實作類別,建構了能應對多樣化情境的機制。數據的建立、刪除、搜尋、更新等操作可以直覺地進行,這也是 List 受到支持的原因之一。

本文的目的與目標讀者

本文將從基礎到應用,系統地、易於初學者理解的方式解說「Java List」。主要針對以下讀者:

  • 剛開始學習 Java,對 List 的使用感到不安的人
  • 想清楚理解陣列(Array)與 List 差異的人
  • 正在煩惱如何區分使用 ArrayList 和 LinkedList 的人
  • 想在實際工作中重新複習 List 基礎知識的人

希望讀完本文後,能確實掌握 Java 中 List 的基本概念、實作方法和具體操作,並能自信地進行程式編寫。

從下一章開始,將首先從「什麼是 List?」這個基本部分開始,依序進行說明。

2. 什麼是 List?

List 的概述與特點

在 Java 中,「List」是以順序保持元素的集合介面。最大的特點是,元素的增加順序會被保留,並且可以使用索引(從 0 開始)來存取個別元素

List 作為集合框架的一部分提供,具有以下功能:

  • 允許元素重複
  • 可以通過指定索引來獲取、更新、刪除元素
  • 可以動態地增加或減少元素數量(與陣列不同,不是固定長度)

這使得數據操作更加靈活,在實際工作中也經常使用。

與陣列(Array)的差異

在 Java 中,陣列(int[]String[] 等)也作為存放多個值的方式存在,但與 List 有一些不同之處。

比較項目陣列(Array)List
元素數量變更不可(固定長度)可(可動態增減)
提供的功能最少的操作(索引存取、獲取長度)豐富的方法(add, remove, contains 等)
型別可以處理基本型別僅限物件型別(需要使用包裹類別)
型別安全性陣列在編譯時進行型別檢查可以使用泛型嚴格指定型別

因此,List 作為更靈活且功能更強大的集合,在許多情況下比陣列更具實用性。

List 介面及其實作類別

在 Java 中使用 List 時,通常使用 List 介面來宣告變數,並使用具體類別(實作類別)來建立實例。代表性的實作類別如下:

  • ArrayList
    結構類似於陣列,可以實現高速存取。擅長數據搜尋和隨機存取。
  • LinkedList
    每個元素都與前後元素相連。插入和刪除速度快,適合經常進行操作的列表。
  • Vector
    類似於 ArrayList,但它是執行緒安全的,相對來說效率較低。目前較少使用。

一般來說,除非有特殊原因,否則ArrayList 是最常用的。您可以根據用途參考後面的效能比較來區分使用。

3. List 的基本用法

本節將依序解說在 Java 中使用 List 的基本操作。這裡主要以ArrayList 為例,介紹 List 的代表性操作。

List 的宣告與初始化

首先,是使用 ArrayList 對 List 進行基本宣告與初始化。

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
    }
}

通常使用 List 介面宣告變數,並使用 ArrayList 建立實例。使用泛型指定要儲存的型別(這裡指定為 String)。

元素的新增(add)

若要將元素新增至 List,請使用 add() 方法。

fruits.add("りんご");
fruits.add("バナナ");
fruits.add("みかん");

這樣,三個元素將依序新增到 List 中。List 會保留元素的增加順序

元素的獲取(get)

若要獲取指定索引的元素,請使用 get(int index)

System.out.println(fruits.get(0)); // 「りんご」が表示される

請注意,索引是從 0 開始

元素的更新(set)

若要更新某個位置的元素,請使用 set(int index, E element)

fruits.set(1, "ぶどう"); // 2番目の要素「バナナ」が「ぶどう」に置き換わる

元素的刪除(remove)

也可以刪除特定索引或元素本身。

fruits.remove(0);           // 先頭の要素を削除
fruits.remove("みかん");    // 「みかん」を削除(最初に一致したものだけ)

獲取 List 的大小(size)

可以使用 size() 方法獲取當前元素的數量。

System.out.println(fruits.size()); // 2などが返る

檢查元素是否存在(contains)

若要檢查 List 是否包含特定元素,請使用 contains()

if (fruits.contains("ぶどう")) {
    System.out.println("ぶどうはあります");
}

總結:常用基本操作一覽

操作方法範例說明
新增add("元素")新增至最後
獲取get(索引)元素的參考
更新set(索引, 新元素)變更指定位置的元素
刪除remove(索引/元素)刪除指定的元素
獲取大小size()獲取元素數量
檢查存在contains("元素")確認特定元素是否存在

4. List 的操作範例

本章將介紹使用 Java List 的實際操作範例。需要依序處理列表內元素的情況非常多,這裡將介紹使用for 迴圈、增強型 for 迴圈、Stream API 的代表性方法。

使用 for 迴圈遍歷

最基本的方法是使用 for 迴圈,通過索引來取出元素。

List<String> fruits = new ArrayList<>();
fruits.add("りんご");
fruits.add("バナナ");
fruits.add("みかん");

for (int i = 0; i < fruits.size(); i++) {
    System.out.println(fruits.get(i));
}

這種方法可以進行精確的索引控制。例如,在只想處理偶數位置的元素時非常有效。

使用增強型 for 迴圈(for-each)遍歷

如果想不考慮索引,依序處理所有元素,那麼增強型 for 迴圈非常方便。

for (String fruit : fruits) {
    System.out.println(fruit);
}

寫法簡單易讀,是最常用的方法之一。對於簡單的處理,這種方法足夠應付。

使用 Lambda 表達式和 Stream API 遍歷

從 Java 8 開始,也可以使用 Stream API 和 Lambda 表達式進行編寫。

fruits.stream().forEach(fruit -> System.out.println(fruit));

這種寫法的優點是可以將多個處理串連起來。例如,可以輕鬆地先根據特定條件過濾再輸出。

fruits.stream()
      .filter(fruit -> fruit.contains("ん"))
      .forEach(System.out::println);

這個範例中,只輸出包含「ん」的水果。特別推薦給想習慣函數式風格編程的人

各種方法的區分使用

方法優點適用場景
傳統 for 迴圈可進行索引控制需要元素編號的處理
增強型 for 迴圈寫法簡單易讀簡單的遍歷處理
Stream API擅長條件式處理和連續處理想組合過濾、映射、聚合等操作的情況

5. ArrayList 與 LinkedList 的差異與區分使用

作為實作 Java List 介面的代表性類別,有ArrayListLinkedList。兩者都可以作為 List 使用,但由於內部結構和效能特性的差異,在適當的場合區分使用非常重要。

ArrayList 的特點與適用用途

ArrayList 內部使用動態陣列(可變長度陣列)

主要特點:

  • 隨機存取(指定索引)速度非常快
  • 若為列表末尾新增元素速度快(平均 O(1))
  • 中間插入或刪除會變慢(O(n))

適用場景:

  • 經常進行搜尋(get())的場景
  • 元素數量可以在事前一定程度上預測的場景
  • 元素新增和刪除較少,以讀取為主的處理
List<String> list = new ArrayList<>();

LinkedList 的特點與適用用途

LinkedList 使用雙向連結列表的結構實現。

主要特點:

  • 元素的新增和刪除速度快(特別是開頭或結尾)
  • 隨機存取(get(index))速度慢(O(n))
  • 記憶體消耗量比 ArrayList 稍微多一些

適用場景:

  • 經常進行元素插入和刪除的場景(特別是開頭或中間)
  • 想作為佇列(Queue)或堆疊(Stack)使用時
  • 以疊代為主,不需要索引存取的情況
List<String> list = new LinkedList<>();

效能比較

下表顯示常用操作的理論計算複雜度(大 O 表示法)

操作ArrayListLinkedList
get(int index)O(1)O(n)
add(E e)(末尾)O(1)O(1)
add(int index, E e)O(n)O(n)
remove(int index)O(n)O(n)
疊代O(n)O(n)

※ 實際處理時間也受到數據大小、JVM 優化等因素影響。

實際應用中區分使用的重點

  • 如果「將數據視為列表,並透過索引存取」,則使用 ArrayList
  • 如果「經常在開頭或中間插入・刪除」,則使用 LinkedList
  • 在效能要求嚴苛的處理中,務必進行基準測試並驗證

6. List 的進階用法

本節將介紹讓 Java List 使用更方便的進階技巧。List 不僅僅是數據的集合體,還可以通過排序、洗牌、過濾、轉換等方式進行多種處理

List 的排序(Collections.sort)

使用 Collections.sort() 可以將 List 中的元素按升序排序。元素需要實作 Comparable 介面。

import java.util.*;

List<String> fruits = new ArrayList<>();
fruits.add("バナナ");
fruits.add("りんご");
fruits.add("みかん");

Collections.sort(fruits);

System.out.println(fruits); // [みかん, りんご, バナナ]

以自訂順序排序時(使用 Comparator)

fruits.sort(Comparator.reverseOrder()); // 降順に並び替え

List 的洗牌(Collections.shuffle)

若要隨機打亂元素的順序,可以使用 Collections.shuffle()

Collections.shuffle(fruits);
System.out.println(fruits); // [バナナ, みかん, りんご](例)

在需要遊戲牌組或隨機顯示順序時很方便。

使用 Stream API 過濾(filter)

使用 Java 8 開始的 Stream,可以簡潔地編寫只提取符合條件的元素的處理。

List<String> filtered = fruits.stream()
    .filter(fruit -> fruit.contains("ん"))
    .collect(Collectors.toList());

System.out.println(filtered); // [みかん, りんご]

使用 Stream API 轉換(map)

若要將元素轉換為其他形式,請使用 map()

List<Integer> lengths = fruits.stream()
    .map(String::length)
    .collect(Collectors.toList());

System.out.println(lengths); // 各果物名の文字数 [3, 3, 2] など

map() 在數據的格式轉換和預處理中是強大的工具。

進階操作總結

操作使用範例主要用途
排序Collections.sort(list)按升序排列
洗牌Collections.shuffle(list)隨機打亂元素的順序
過濾stream().filter(...).collect()只提取符合條件的元素
轉換stream().map(...).collect()轉換元素的型別或值

7. 常見錯誤及其處理方法

在使用 Java List 時,初學者特別容易遇到的問題是「例外(錯誤)」。這裡將具體解說實際上經常發生的代表性錯誤、其原因和解決方法。

IndexOutOfBoundsException(索引超出範圍錯誤)

發生原因:

嘗試存取不存在的索引時發生。

List<String> list = new ArrayList<>();
list.add("りんご");

System.out.println(list.get(1)); // エラー:Index 1 out of bounds

處理方法:

在存取前確認大小,或通過條件判斷來控制索引是否有效。

if (list.size() > 1) {
    System.out.println(list.get(1));
}

NullPointerException(空指標例外)

發生原因:

在 List 本身或 List 的元素為 null 的狀態下呼叫方法時發生。

List<String> list = null;
list.add("りんご"); // NullPointerException発生

處理方法:

事先確認變數不是 null,或利用 Optional 等。

或者,注意不要忘記初始化:

List<String> list = new ArrayList<>(); // 正しい初期化

ConcurrentModificationException(並發修改例外)

發生原因:

在使用 for-each 迴圈或 Iterator 遍歷 List 時,直接修改 List 時發生。

for (String fruit : list) {
    if (fruit.equals("バナナ")) {
        list.remove(fruit); // ConcurrentModificationException
    }
}

處理方法:

使用 Iterator 安全刪除,或使用 removeIf() 等方法。

或者,從 Java 8 開始可以更簡潔:

list.removeIf(fruit -> fruit.equals("バナナ"));

其他注意事項

  • 確認 List 不是 null
  • 僅宣告變數但未使用的情況非常多。初始化是必須的。

  • 理解索引從 0 開始
  • 這是初學者常見的錯誤,容易誤以為「第一個是索引 1」。

錯誤處理總結

錯誤名稱主要原因處理方法範例
IndexOutOfBoundsException存取不存在的索引使用 size() 確認長度
NullPointerExceptionList 或元素為 null不要忘記初始化,並進行 null 檢查
ConcurrentModificationException遍歷期間直接修改 List使用 Iterator 操作或利用 removeIf()

8. 總結

回顧 Java List 的基礎知識

本文循序漸進地解說了 Java 中 List 的基礎到應用。List 在 Java 集合中是使用頻率特別高的,是靈活處理數據的重要工具

首先,在掌握了什麼是 List 的基礎上,學習了以下重點:

  • List 是有順序、允許重複的集合,可以進行索引操作
  • ArrayList 和 LinkedList 兩個代表性的實作類別,特點和用途不同
  • 掌握基本操作(新增、獲取、更新、刪除、搜尋)後,可以自由地操作數據
  • for 迴圈、增強型 for 迴圈、Stream API 等,根據情況進行重複處理
  • 也能應對排序、過濾、轉換等進階處理
  • 理解常見錯誤及其原因和處理方法,可以預防問題發生

ArrayList 與 LinkedList 的區分使用

應該使用哪種 List 實作,根據處理內容和數據量適當選擇非常重要。以下判斷可作為參考:

  • ArrayList:隨機存取多,以讀取為主
  • LinkedList:經常發生插入・刪除,存取順序很重要

未來學習的方向

List 僅僅是 Java 集合的「入口」。為了處理更進階的數據結構和工具,建議深入理解以下類別和功能:

  • Set・Map:管理唯一元素、鍵值對結構
  • Collections 工具類別:排序、獲取最小・最大值等
  • Stream API 的活用:引入函數式編程
  • 泛型的理解:型別安全的集合操作

掌握 List 的基礎,Java 程式設計整體會變得更容易掌握

常見問題(FAQ)

整理了關於 Java List 初學者特別容易疑問的重點。嚴選了實際工作中也經常遇到的內容。

Q1. Java 的 List 與陣列(Array)有什麼不同?

A. 陣列的元素數量是固定的,需要在宣告時決定大小。另一方面,List 的大小是可變的,可以靈活地新增或刪除元素。此外,List 提供了許多方便的方法(add, remove, contains 等),在可讀性和維護性方面也更優越。

Q2. ArrayList 與 LinkedList 應該使用哪一個?

A. 主要在隨機存取(指定索引獲取)較多的情況下適合使用 ArrayList,在經常發生元素插入・刪除的情況下適合使用 LinkedList。如果猶豫不決,通常從 ArrayList 開始使用。

Q3. List 可以存放基本型別(int 或 double 等)嗎?

A. 不能直接存放。Java 的 List 只能處理物件型別,因此對於 int 等基本型別,需要使用對應的包裹類別(Integer, Double 等)。

List<Integer> numbers = new ArrayList<>();
numbers.add(10); // オートボクシングされてInteger型として格納される

Q4. 想對 List 中的元素進行排序,應該怎麼做?

A. 使用 Collections.sort(list) 可以按升序排序。此外,如果想按自訂順序排序,可以指定 Comparator 來自由排序。

Q5. 如果想管理不重複的元素,應該怎麼做?

A. List 是允許重複的集合。若要避免重複,請考慮使用 Set(例如:HashSet)。但需要注意,Set 不保證順序。如果想在 List 中排除重複,也可以使用如下的 Stream 處理:

List<String> distinctList = list.stream()
    .distinct()
    .collect(Collectors.toList());

Q6. 如果想清除 List 中的所有內容,應該怎麼做?

A. 使用 clear() 方法可以刪除 List 中的所有元素。

list.clear();

Q7. 在 List 中最常用的操作是什麼?

A. 在實際應用中最常用的是 add(新增)、get(獲取)、remove(刪除)、size(獲取大小)。掌握了這些,基本處理就可以幾乎涵蓋。