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 ObjectOutputStream và ObjectInputStream.
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.
GO TO FULL VERSION