1. Giới thiệu
Trong Java, việc tuần tự hóa chỉ hoạt động với những đối tượng đã cho phép rõ ràng. Để làm được điều đó, lớp phải triển khai một interface đặc biệt — java.io.Serializable.
import java.io.Serializable;
public class Person implements Serializable {
// Trường, constructor, phương thức
}
Serializable — là một interface đánh dấu: nó không có phương thức nào; nó chỉ báo cho JVM — “lớp này có thể được tuần tự hóa, đừng lo!”. Nếu bạn thử tuần tự hóa một đối tượng của lớp không triển khai Serializable, bạn sẽ nhận NotSerializableException. Ngay cả khi chỉ một trường (hoặc đối tượng lồng bên trong) không thể tuần tự hóa — quá trình tuần tự hóa sẽ không hoạt động.
ObjectOutputStream và ObjectInputStream
- ObjectOutputStream — lớp ghi các đối tượng vào luồng (ví dụ, vào tệp hoặc qua mạng).
- ObjectInputStream — lớp đọc các đối tượng từ luồng.
Chúng hoạt động theo cặp: một cái tuần tự hóa đối tượng, cái còn lại — giải tuần tự hóa.
Các phương thức chính
- writeObject(Object obj) — tuần tự hóa đối tượng và ghi nó vào luồng.
- readObject() — đọc đối tượng từ luồng, giải tuần tự hóa và trả về nó.
Quan trọng: cả hai lớp đều hoạt động trên các luồng I/O thông thường (OutputStream và InputStream). Thường chúng được dùng cùng với các luồng tệp — FileOutputStream và FileInputStream, nhưng cũng có thể dùng với các luồng mạng.
2. Ví dụ về tuần tự hóa
Hãy viết một ví dụ đơn giản: tuần tự hóa và giải tuần tự hóa một đối tượng của lớp Person.
Bước 1. Mô tả lớp
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
// trường transient sẽ không được tuần tự hóa
private transient String secret;
public Person(String name, int age, String secret) {
this.name = name;
this.age = age;
this.secret = secret;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", secret='" + secret + "'}";
}
}
Bước 2. Tuần tự hóa đối tượng vào tệp
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeDemo {
public static void main(String[] args) throws Exception {
Person person = new Person("Alice", 30, "likes pizza");
// Tạo luồng để ghi vào tệp
FileOutputStream fileOut = new FileOutputStream("person.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
// Lưu đối tượng
out.writeObject(person);
// Đóng các luồng
out.close();
fileOut.close();
System.out.println("Đối tượng đã được tuần tự hóa vào tệp person.bin");
}
}
Bước 3. Giải tuần tự hóa đối tượng từ tệp
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializeDemo {
public static void main(String[] args) throws Exception {
// Mở luồng để đọc từ tệp
FileInputStream fileIn = new FileInputStream("person.bin");
ObjectInputStream in = new ObjectInputStream(fileIn);
// Phục hồi đối tượng
Person person = (Person) in.readObject();
in.close();
fileIn.close();
System.out.println("Đối tượng đã được giải tuần tự: " + person);
}
}
Đầu ra mong đợi
Đối tượng đã được tuần tự hóa vào tệp person.bin
Đối tượng đã được giải tuần tự: Person{name='Alice', age=30, secret='null'}
Lưu ý! Trường transient không được tuần tự hóa. Sau khi giải tuần tự, nó sẽ có giá trị null. Điều này quan trọng đối với dữ liệu tạm thời hoặc nhạy cảm.
3. Hạn chế và đặc điểm
Tất cả các trường phải có thể tuần tự hóa
Nếu một lớp có các trường không tự triển khai Serializable (hoặc chứa các đối tượng như vậy), quá trình tuần tự hóa sẽ kết thúc với lỗi. Ví dụ, các trường kiểu Thread hoặc Socket không thể “đơn giản” làm cho trở nên có thể tuần tự hóa.
Trường static và transient
- Trường tĩnh (static) không được tuần tự hóa: chúng thuộc về lớp, không thuộc về một đối tượng cụ thể.
- Trường transient — những trường được đánh dấu transient sẽ bị loại trừ khỏi quá trình tuần tự hóa và sau khi phục hồi sẽ nhận giá trị mặc định (null, 0, v.v.).
Ngoại lệ
- Cố gắng tuần tự hóa một đối tượng không triển khai Serializable sẽ gây ra NotSerializableException.
- Khi giải tuần tự hóa có thể xảy ra các lỗi: không tìm thấy tệp, không khớp lớp, dữ liệu bị hỏng, v.v.
Phiên bản lớp
Nếu bạn thay đổi cấu trúc của lớp sau khi đã tuần tự hóa (ví dụ, thêm/xóa các trường), khi giải tuần tự hóa có thể phát sinh InvalidClassException. Để kiểm soát phiên bản, sử dụng trường đặc biệt serialVersionUID (sẽ nói chi tiết hơn trong một bài giảng sau).
4. Thực hành: tuần tự hóa và giải tuần tự hóa đối tượng vào tệp
Giả sử chúng ta có lớp Person, và muốn lưu danh sách người vào tệp rồi đọc lại.
Lớp Person (có thể tuần tự hóa)
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
private transient String secret; // sẽ không được tuần tự hóa
public Person(String name, int age, String secret) {
this.name = name;
this.age = age;
this.secret = secret;
}
@Override
public String toString() {
return name + " (" + age + "), secret: " + secret;
}
}
Tuần tự hóa danh sách người
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class SerializeListDemo {
public static void main(String[] args) throws Exception {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30, "likes pizza"));
people.add(new Person("Bob", 25, "hates broccoli"));
FileOutputStream fileOut = new FileOutputStream("people.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
// Lưu danh sách
out.writeObject(people);
out.close();
fileOut.close();
System.out.println("Danh sách người đã được tuần tự hóa.");
}
}
Giải tuần tự hóa danh sách người
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
public class DeserializeListDemo {
public static void main(String[] args) throws Exception {
FileInputStream fileIn = new FileInputStream("people.bin");
ObjectInputStream in = new ObjectInputStream(fileIn);
// Phục hồi danh sách
List<Person> people = (List<Person>) in.readObject();
in.close();
fileIn.close();
for (Person p : people) {
System.out.println(p);
}
}
}
Kết quả:
Alice (30), secret: null
Bob (25), secret: null
5. Lỗi thường gặp
Lỗi số 1: Lớp không triển khai Serializable. Nếu quên thêm implements Serializable, khi cố gắng tuần tự hóa bạn sẽ nhận NotSerializableException. Đây là lỗi đơn giản và thường gặp nhất.
Lỗi số 2: Trường không thể tuần tự hóa. Nếu đối tượng chứa một trường không thể tuần tự hóa (ví dụ, Thread, Socket hoặc bất kỳ kiểu nào khác không có Serializable), quá trình tuần tự hóa sẽ “sập”. Hãy đánh dấu những trường đó là transient hoặc làm cho chúng có thể tuần tự hóa.
Lỗi số 3: Thay đổi cấu trúc lớp. Nếu đối tượng đã được tuần tự hóa và sau đó lớp bị thay đổi (thêm/xóa trường), khi đọc có thể xảy ra InvalidClassException. Hãy chỉ định serialVersionUID để kiểm soát phiên bản ổn định.
Lỗi số 4: Cố gắng tuần tự hóa các trường static. Các trường có modifier static không được tuần tự hóa. Sau khi giải tuần tự, giá trị của chúng sẽ là giá trị mặc định của lớp chứ không phải trạng thái tại thời điểm tuần tự hóa.
Lỗi số 5: Không đóng luồng. Nếu không đóng các luồng sau khi sử dụng, bạn có thể gặp tệp bị hỏng hoặc rò rỉ tài nguyên. Hãy dùng try-with-resources hoặc đóng luồng một cách tường minh.
Lỗi số 6: Không khớp lớp. Nếu lớp đã được đổi tên hoặc di chuyển sang package khác, việc giải tuần tự sẽ không hoạt động — cần trùng khớp chính xác tên và package của lớp đã được lưu trong luồng.
GO TO FULL VERSION