Trong lập trình Java, tuần tự hóa (serialization) là quá trình chuyển đổi trạng thái của một đối tượng thành chuỗi byte để lưu trữ hoặc truyền tải. Ngược lại, khôi phục (deserialization) là quá trình tái tạo lại đối tượng từ chuỗi byte đó.
Lý do cần tuần tự hóa
Đối tượng Java chỉ tồn tại trong bộ nhớ heap của JVM, không thể trực tiếp chia sẻ giữa các tiến trình hay gửi qua mạng. Tuần tự hóa giúp biến đối tượng thành dữ liệu nhị phân có thể:
- Gửi giữa các tiến trình (RPC, RMI)
- Truyền qua mạng
- Lưu trữ lâu dài vào file hoặc database
Cách triển khai với JDK Serialization
Cách đơn giản nhất là cho lớp cần tuần tự hóa implements interface Serializable. Đây là marker interface – không chứa phương thức nào, chỉ dùng để đánh dấu.
Ví dụ minh họa:
class AddressInfo implements Serializable {
private String location;
// getter/setter
}
class Institution {
private String name;
// getter/setter — không implements Serializable → sẽ gây lỗi nếu được tham chiếu
}
class BaseEntity implements Serializable {
protected String category;
// getter/setter
}
class Member extends BaseEntity implements Serializable {
private static boolean isActive; // static → không được serialize
private Long id;
private int years;
private String loginName;
private transient String ssn; // transient → bị bỏ qua
private Date registeredAt;
private AddressInfo residence; // OK vì implements Serializable
private Institution organization; // LỖI nếu gán giá trị
private List<Member> connections; // OK nếu các phần tử đều serializable
// getter/setter
}
Khi chạy thử, nếu cố gắng serialize trường organization (không serializable), chương trình sẽ ném NotSerializableException. Tương tự, nếu lớp cha BaseEntity không implements Serializable, trường category sẽ bị mất sau khi deserialize.
Nguyên lý hoạt động bên trong
Quá trình thực hiện bởi ObjectOutputStream và ObjectInputStream.
Serialize:
writeObject()→ gọiwriteObject0()- Xác định kiểu dữ liệu: String, mảng, enum → xử lý riêng
- Đối tượng thông thường → gọi
writeOrdinaryObject() - Kiểm tra xem lớp có ghi đè
writeObject()không → nếu có thì invoke qua reflection - Nếu không → gọi
defaultWriteFields()để ghi từng field - Field là object → đệ quy tiếp
Deserialize:
readObject()→ gọireadObject0()- Đọc byte đầu tiên để xác định kiểu dữ liệu
- Với object → gọi
readOrdinaryObject() - Dùng reflection tạo instance qua constructor không tham số
- Kiểm tra xem có ghi đè
readObject()không → nếu có thì invoke - Nếu không → gọi
defaultReadFields()để gán giá trị từng field
Tuỳ chỉnh quá trình serialize/deserialize
Có thể ghi đè hai phương thức private sau để kiểm soát chi tiết:
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeLong(this.id);
out.writeObject(this.loginName);
out.writeObject(this.ssn); // dù là transient nhưng vẫn ghi thủ công
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
this.id = in.readLong();
this.loginName = (String) in.readObject();
this.ssn = (String) in.readObject();
}
Lưu ý:
- Phải ghi đè cả hai phương thức
- Thứ tự đọc/ghi phải khớp nhau
- Phải là
private— modifier khác sẽ không có hiệu lực
Dùng Externalizable để kiểm soát toàn bộ
Thay vì Serializable, có thể implements Externalizable và bắt buộc override:
public void writeExternal(ObjectOutput out) throws IOException {
out.writeLong(id);
out.writeObject(loginName);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.id = in.readLong();
this.loginName = (String) in.readObject();
}
Xử lý vấn đề với Singleton
Tuần tự hóa có thể phá vỡ mẫu Singleton vì sẽ tạo instance mới khi deserialize. Giải pháp: thêm phương thức readResolve():
public class UniqueInstance implements Serializable {
private static final UniqueInstance INSTANCE = new UniqueInstance();
public static UniqueInstance getInstance() { return INSTANCE; }
private Object readResolve() {
return INSTANCE; // luôn trả về instance duy nhất
}
}
Phương thức này phải trả về Object — nếu trả về kiểu cụ thể (UniqueInstance) thì sẽ không được gọi.
Vai trò của serialVersionUID
Là ID phiên bản lớp, dùng để kiểm tra tính tương thích khi deserialize. Nếu không khai báo, JVM sẽ tự sinh — nhưng dễ gây lỗi nếu class thay đổi. Nên khai báo tường minh:
private static final long serialVersionUID = 1L;
Nếu version không khớp, sẽ ném InvalidClassException.
So sánh với các cơ chế khác
| Cơ chế | Kích thước | Tốc độ | Ưu điểm |
|---|---|---|---|
| JDK | Lớn | Chậm | Không cần thư viện ngoài, đáng tin cậy |
| Hessian | Trung bình | Khá nhanh | Hỗ trợ đa ngôn ngữ, tối ưu tốt hơn JDK |
| JSON | Lớn | Khá nhanh | Dễ đọc, hỗ trợ đa ngôn ngữ |
| Kryo | Rất nhỏ | Rất nhanh | Hiệu năng cao, nhưng chỉ dùng trong Java |
| ProtoBuf | Rất nhỏ | Rất nhanh | Hiệu năng cao, hỗ trợ đa ngôn ngữ, cần định nghĩa schema trước |
JDK Serialization phù hợp cho các ứng dụng nội bộ Java cần độ tin cậy cao, trong khi các giải pháp khác tối ưu hơn cho hiệu năng và kích thước.