CodeGym /Các khóa học /JAVA 25 SELF /Giao diện Serializable: nguyên tắc cơ bản

Giao diện Serializable: nguyên tắc cơ bản

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Giao diện Serializable

Bạn còn nhớ các ví dụ từ bài giảng trước không? Các lớp Java mà chúng ta muốn tuần tự hóa phải triển khai giao diện đặc biệt — java.io.Serializable. Đây là giao diện đánh dấu (marker interface): nó không chứa bất kỳ phương thức nào, mà chỉ “đánh dấu” lớp là phù hợp để tuần tự hóa. Nếu lớp triển khai giao diện này, JVM cho phép tuần tự hóa các đối tượng của nó bằng các công cụ tiêu chuẩn.

Không nên tuần tự hóa bừa bãi mọi thứ, vì không phải mọi đối tượng đều có thể hoặc cần được lưu thành byte. Một số đối tượng phụ thuộc vào trạng thái hệ điều hành, tệp đang mở hoặc kết nối mạng. Vì vậy Java yêu cầu phải đánh dấu rõ ràng lớp là có thể tuần tự hóa.

Giao diện đánh dấu giống như một nhãn “được phép đóng gói” trên hộp. Nếu không có nhãn như vậy — “người đóng gói” (JVM) sẽ từ chối làm việc.

Ví dụ: khai báo một lớp có thể tuần tự hóa

import java.io.Serializable;

public class User implements Serializable {
    private String name;
    private int age;

    // Constructor, getter và setter
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Cho đẹp: phương thức toString()
    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

Lưu ý:

  • Chúng ta chỉ cần thêm implements Serializable vào khai báo lớp.
  • Không cần triển khai bất kỳ phương thức nào (giao diện rỗng).
  • Tất cả các lớp chuẩn của Java có thể tuần tự hóa (ví dụ, ArrayList, HashMap, String) đều đã triển khai Serializable.

2. Cách làm cho lớp của bạn có thể tuần tự hóa

Quy tắc №1: Chỉ cần thêm implements Serializable

Đó là tất cả những gì cần cho bản thân lớp. Nhưng có những lưu ý!

Quan trọng: tất cả các đối tượng lồng nhau cũng phải có thể tuần tự hóa.
Nếu lớp của bạn có các trường tham chiếu đến các đối tượng khác, chúng cũng phải có thể tuần tự hóa. Ví dụ:

public class Profile implements Serializable {
    private User user; // User phải có thể tuần tự hóa!
    private int level;
}

Nếu ít nhất một trường không thể tuần tự hóa, khi cố gắng tuần tự hóa sẽ xảy ra ngoại lệ.

3. Ví dụ về tuần tự hóa và giải tuần tự hóa

Hãy xem cách tuần tự hóa và giải tuần tự hóa một đối tượng vào/ra tệp. Để làm điều này, ta dùng các lớp ObjectOutputStreamObjectInputStream.

Ví dụ: tuần tự hóa đối tượng User vào tệp

import java.io.*;

public class SerializeDemo {
    public static void main(String[] args) {
        User user = new User("Alice", 30);

        // Lưu đối tượng vào tệp
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
            oos.writeObject(user);
            System.out.println("Đối tượng đã được tuần tự hóa vào tệp user.ser");
        } catch (IOException e) {
            System.out.println("Lỗi tuần tự hóa: " + e.getMessage());
        }
    }
}

Điều gì đang xảy ra ở đây?

  • Tạo đối tượng User.
  • Mở luồng ObjectOutputStream, ghi vào tệp "user.ser".
  • Gọi writeObject(user). Tại thời điểm này JVM chuyển đối tượng thành luồng byte và lưu nó vào tệp.

Ví dụ: giải tuần tự hóa đối tượng từ tệp

import java.io.*;

public class DeserializeDemo {
    public static void main(String[] args) {
        // Đọc đối tượng từ tệp
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
            User user = (User) ois.readObject();
            System.out.println("Đối tượng đã được khôi phục thành công: " + user);
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("Lỗi giải tuần tự hóa: " + e.getMessage());
        }
    }
}

Điều gì đang xảy ra ở đây?

  • Mở luồng ObjectInputStream, đọc từ tệp "user.ser".
  • Gọi readObject(). JVM khôi phục đối tượng từ các byte.
  • Đừng quên ép kiểu kết quả về kiểu mong muốn (User), vì readObject() trả về Object.
  • Có thể xảy ra ClassNotFoundException nếu lớp User không được tìm thấy khi giải tuần tự hóa.

Tất cả cùng nhau: tuần tự hóa và giải tuần tự hóa

import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        User user = new User("Bob", 22);

        // Tuần tự hóa
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
            oos.writeObject(user);
            System.out.println("Tuần tự hóa hoàn tất!");
        } catch (IOException e) {
            System.out.println("Lỗi tuần tự hóa: " + e.getMessage());
        }

        // Giải tuần tự hóa
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
            User loaded = (User) ois.readObject();
            System.out.println("Giải tuần tự hóa hoàn tất! " + loaded);
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("Lỗi giải tuần tự hóa: " + e.getMessage());
        }
    }
}

Kết quả:

Tuần tự hóa hoàn tất!
Giải tuần tự hóa hoàn tất! User{name='Bob', age=22}

4. Điều gì diễn ra “bên dưới nắp máy” khi tuần tự hóa

Khi bạn gọi writeObject, JVM trước hết kiểm tra lớp có triển khai giao diện Serializable hay không. Nếu lớp không được đánh dấu là có thể tuần tự hóa, sẽ ném ra ngoại lệ. Sau đó JVM duyệt qua tất cả các trường thông thường của đối tượng (tức là những trường không static và không transient) và ghi giá trị của chúng vào luồng byte. Nếu trong số các trường đó có các đối tượng khác, quá trình tuần tự hóa được áp dụng đệ quy cho chúng, nhưng chỉ khi chúng cũng triển khai Serializable.

Khi giải tuần tự hóa, đối tượng được tạo ra mà không gọi constructor thông thường, và các trường của nó được điền bằng các giá trị đã lưu — như thể một “constructor không constructor” hồi sinh đối tượng từ luồng byte.

Một số trường sẽ không được tuần tự hóa. Các trường tĩnh (static) thuộc về chính lớp chứ không phải từng đối tượng, nên giá trị của chúng không được lưu. Các trường được đánh dấu transient cũng bị bỏ qua — điều này thuận tiện cho dữ liệu tạm thời, bộ nhớ đệm hoặc thông tin bí mật như mật khẩu.

Sơ đồ quy trình tuần tự hóa

flowchart TB
    A[Đối tượng User trong bộ nhớ] -- writeObject --> B[ObjectOutputStream]
    B -- lưu các byte --> C[Tệp user.ser]
    C -- readObject --> D[ObjectInputStream]
    D -- khôi phục --> E[Đối tượng User trong bộ nhớ]

5. Những lỗi thường gặp khi sử dụng Serializable

Lỗi №1: Trường tham chiếu tới đối tượng không thể tuần tự hóa. Nếu trong lớp User xuất hiện một trường thuộc kiểu, ví dụ, Thread hoặc Socket, tuần tự hóa sẽ không hoạt động. Không phải mọi đối tượng đều có thể tuần tự hóa — hãy ghi nhớ điều này!

Lỗi №2: Các lớp lồng không tuần tự hóa được. Nếu lớp User chứa một lớp bên trong không static, thì việc tuần tự hóa có thể không hoạt động. Tốt hơn hãy sử dụng các lớp lồng static hoặc các lớp riêng biệt.

Lỗi №3: Cố gắng tuần tự hóa trường static. Các trường tĩnh không được tuần tự hóa — chúng thuộc về lớp chứ không phải đối tượng. Sau khi giải tuần tự hóa, trường static sẽ có giá trị được xác định trong lớp, chứ không phải trong đối tượng đã tuần tự hóa.

Lỗi №4: Không khớp phiên bản lớp. Nếu thay đổi cấu trúc của lớp sau khi tuần tự hóa (ví dụ, thêm hoặc xóa một trường), rồi cố gắng giải tuần tự hóa đối tượng cũ, có thể xảy ra lỗi InvalidClassException. Để kiểm soát phiên bản, người ta sử dụng trường đặc biệt serialVersionUID — chúng ta sẽ bàn chi tiết hơn về nó trong bài giảng tiếp theo.

1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Giao diện đánh dấu Serializable
Giao diện đánh dấu Serializable
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Tuần tự hóa và giải tuần tự hóa đơn giản
Tuần tự hóa và giải tuần tự hóa đơn giản
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION