CodeGym /課程 /JAVA 25 SELF /集合的序列化:List、Map、Set

集合的序列化:List、Map、Set

JAVA 25 SELF
等級 42 , 課堂 4
開放

1. 集合的序列化

好消息:幾乎所有 Java 標準集合(例如 ArrayListHashSetHashMap 等)都已經實作了 Serializable。這意味著你可以直接開箱序列化它們,無須大費周章。

範例:

import java.io.Serializable;
import java.util.ArrayList;

public class MyList extends ArrayList<String> implements Serializable {
    // 甚至什麼都不用寫 — ArrayList 已經是 Serializable!
}

實務上你通常會序列化標準集合,而且一切都能順利運作,省去多餘的麻煩。

如何序列化集合?

序列化集合的方式與序列化任何其他物件相同:

  • 建立集合。
  • 透過 ObjectOutputStream 將其寫入檔案。
  • 透過 ObjectInputStream 讀回。

範例 1:序列化 ArrayList<String>

import java.io.*;
import java.util.*;

public class SerializeListDemo {
    public static void main(String[] args) throws Exception {
        // 1. 建立字串清單
        List<String> books = new ArrayList<>();
        books.add("麥田捕手");
        books.add("遠大前程");
        books.add("神曲");

        // 2. 將清單儲存到檔案
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("books.ser"));
        out.writeObject(books);
        out.close();

        // 3. 從檔案讀回清單
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("books.ser"));
        List<String> loadedBooks = (List<String>) in.readObject();
        in.close();

        // 4. 檢查結果
        System.out.println("反序列化後的書籍清單:");
        for (String book : loadedBooks) {
            System.out.println("- " + book);
        }
    }
}

輸出:

反序列化後的書籍清單:
- 麥田捕手
- 遠大前程
- 神曲

範例 2:序列化 HashSet<Integer>

Set<Integer> numbers = new HashSet<>(Arrays.asList(10, 20, 30, 40));
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("numbers.ser"));
out.writeObject(numbers);
out.close();

ObjectInputStream in = new ObjectInputStream(new FileInputStream("numbers.ser"));
Set<Integer> loadedNumbers = (Set<Integer>) in.readObject();
in.close();

System.out.println("反序列化後的集合: " + loadedNumbers);

範例 3:序列化 HashMap<String, Integer>

Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 100);
scores.put("Bob", 80);

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("scores.ser"));
out.writeObject(scores);
out.close();

ObjectInputStream in = new ObjectInputStream(new FileInputStream("scores.ser"));
Map<String, Integer> loadedScores = (Map<String, Integer>) in.readObject();
in.close();

System.out.println("反序列化後的 Map: " + loadedScores);

重點

順序是否會保留?

  • 對於 List(例如 ArrayList),元素順序一定會保留。
  • 對於 SetHashSet),順序不保證(序列化前後皆然)。
  • 對於 MapHashMap),鍵值對的順序不保證(若需要順序 — 請使用 LinkedHashMap)。

空集合 可以正常序列化與反序列化。反序列化後你會得到空的清單/集合/映射。

巢狀集合(例如 List<List<String>>)只要所有巢狀元素可被序列化,就能正確序列化。

2. 集合元素的要求

這裡常常潛藏著第一個大陷阱!

集合中的所有元素也必須是可序列化的。

如果集合中至少有一個元素沒有實作 Serializable,則序列化會以 NotSerializableException 失敗。而且如果集合很大、錯誤出在深處,找出元兇會變成一場大冒險。

範例:序列化包含不可序列化物件的集合

import java.io.*;
import java.util.*;

class NotSerializableClass {
    int value;
    public NotSerializableClass(int value) { 
        this.value = value; 
    }
}

public class NotSerializableDemo {
    public static void main(String[] args) throws Exception {
        List<Object> list = new ArrayList<>();
        list.add("Hello");
        list.add(new NotSerializableClass(123)); // <- 糟了!

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("badlist.ser"));
        try {
            out.writeObject(list); // 這裡會丟出例外!
        } catch (NotSerializableException e) {
            System.out.println("序列化錯誤: " + e);
        }
        out.close();
    }
}

結果:

序列化錯誤: java.io.NotSerializableException: NotSerializableClass

怎麼辦?

解法其實很簡單:集合中的所有元素要嘛是標準型別,例如 StringInteger,要嘛是你自己的類別,且這些類別有實作 Serializable。如果某個類別不可序列化,只要在類別上加入 implements Serializable 就能解決問題。

3. 不同集合的序列化特性

元素順序是否會保留?

  • List:會,元素順序會被保留。
  • Set:取決於實作。在 HashSet 中不保證順序,而在 LinkedHashSet 中則會保留。
  • Map:HashMap 中不保證順序,在 LinkedHashMap 中則會保留插入順序。

示範:

List<String> list = Arrays.asList("A", "B", "C");
Set<String> set = new HashSet<>(list);

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("coll.ser"));
out.writeObject(list);
out.writeObject(set);
out.close();

ObjectInputStream in = new ObjectInputStream(new FileInputStream("coll.ser"));
List<String> loadedList = (List<String>) in.readObject();
Set<String> loadedSet = (Set<String>) in.readObject();
in.close();

System.out.println("List: " + loadedList); // 永遠是 [A, B, C]
System.out.println("Set: " + loadedSet);   // 可能是 [A, C, B] 等等

序列化空集合

空集合能夠順利序列化。反序列化後你會得到對應型別的空物件。

序列化巢狀集合

可以序列化包含其他集合的集合:

List<List<String>> table = new ArrayList<>();
table.add(Arrays.asList("row1-col1", "row1-col2"));
table.add(Arrays.asList("row2-col1", "row2-col2"));

// 序列化
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("table.ser"));
out.writeObject(table);
out.close();

// 反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("table.ser"));
List<List<String>> loadedTable = (List<List<String>>) in.readObject();
in.close();

System.out.println(loadedTable);

4. 實作範例:序列化自訂類別物件的集合

我們一起寫個「虛擬圖書館」的小應用,來序列化書籍清單。類別 Book 必須是可序列化的!

import java.io.*;
import java.util.*;

class Book implements Serializable {
    private static final long serialVersionUID = 1L; // 用於類別版本相容性
    String title;
    String author;
    int year;

    public Book(String title, String author, int year) {
        this.title = title;
        this.author = author;
        this.year = year;
    }

    @Override
    public String toString() {
        return title + " (" + author + ", " + year + ")";
    }
}

public class LibraryApp {
    public static void main(String[] args) throws Exception {
        List<Book> books = new ArrayList<>();
        books.add(new Book("麥田捕手", "J. D. 薩林傑", 1951));
        books.add(new Book("遠大前程", "查爾斯·狄更斯", 1861));
        books.add(new Book("神曲", "但丁·阿利吉耶里", 1320));

        // 將清單儲存到檔案
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("mylibrary.ser"));
        out.writeObject(books);
        out.close();

        // 將清單讀回
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("mylibrary.ser"));
        List<Book> loadedBooks = (List<Book>) in.readObject();
        in.close();

        System.out.println("檔案中的書籍:");
        for (Book b : loadedBooks) {
            System.out.println("- " + b);
        }
    }
}

5. 序列化集合的常見錯誤

錯誤 1:集合元素上的 NotSerializableException。 只要集合裡有一個元素不可序列化,就會因 NotSerializableException 而失敗。例如你不小心把沒有實作 Serializable 的物件加進集合,或忘了在自己的類別上加上該介面。

錯誤 2:在序列化與反序列化之間修改了類別。 如果你序列化了一組物件,之後又修改了類別結構(例如新增或刪除欄位),反序列化時可能拋出 InvalidClassException。為避免此問題,請在類別中使用 serialVersionUID 欄位。

錯誤 3:序列化包含 transient 或 static 欄位的集合。 標記為 transientstatic 的欄位不會被序列化。如果你的物件依賴這些欄位,反序列化後它們會是預設值(例如 null0)。

錯誤 4:序列化包含巢狀不可序列化物件的集合。 如果集合中存在巢狀集合或未實作 Serializable 的物件,錯誤會發生在最深層 — 並不總是容易判斷問題點在哪。請檢查所有巢狀層級!

錯誤 5:面對大型集合的效能問題。 如果集合非常龐大,序列化可能會耗費大量時間與磁碟空間。此時應考慮串流式序列化,或將集合分塊處理。

1
問卷/小測驗
序列化,等級 42,課堂 4
未開放
序列化
物件序列化入門
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION