Java OOP – Kĩ thuật Serialization/Deserialization

Serialization trong Java

Tuần tự hoá trong java hay serialization trong java là một cơ chế để ghi trạng thái của một đối tượng vào một byte stream.

Nó chủ yếu được sử dụng trong các công nghệ Hibernate, RMI, JPA, EJB và JMS.

Hoạt động ngược lại của serialization được gọi là deserialization.

Ưu điểm của Serialization trong java

Nó chủ yếu được sử dụng để truyền trạng thái của đối tượng qua mạng (được biết đến như marshaling).

java.io.Serializable interface

Serializable là một interface (giao diện) đánh dấu, không có thuộc tính và phương thức bên trong. Nó được sử dụng để “đánh dấu” các lớp java để các đối tượng của các lớp này có thể nhận được khả năng nhất định. Cloneable và Remote cũng là những interface đánh dấu.

Nó phải được implements bởi lớp mà đối tượng của nó bạn muốn persist.

Lớp String và tất cả các lớp wrapper mặc định implements interface java.io.Serializable.

Hãy xem ví dụ dưới đây:

package com.amzuni;
 
import java.io.Serializable;
 
public class Student implements Serializable {
 
    private static final long serialVersionUID = 1427461703707854023L;
 
    private int id;
    private String name;
 
    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Trong ví dụ trên, lớp Student implements giao tiếp Serializable. Bây giờ các đối tượng của nó có thể được chuyển đổi thành stream.

Xem thêm bài viết ObjectOutputStream trong java để hiểu rõ hơn về Serialization trong java.

Deserialization trong java

Deserialization là quá trình tái thiết lại các đối tượng từ trạng thái serialized. Đây là hoạt động ngược lại của serialization.

Xem thêm bài viết ObjectInputStream trong java để hiểu rõ hơn về Deserialization trong java.

Trong ví dụ đó, các bạn sẽ thấy được một Object có thể được lưu trực tiếp vào một file và có thể đọc từ file để có được một Object hoàn chỉnh như ban đầu chỉ với 2 phương thức đơn giản writeObject() và readObject(). Nếu không có serialization, chúng ta phải lưu từng field của đối tượng vào file như một plain text, sau đó để khôi phục lại giá trị ban đầu của đối tượng, chúng ta phải đọc dữ liệu từ file và tạo một object mới sau đó set giá trị cho từng field của object.

Java Serialization với thừa kế (Mối quan hệ IS-A)

Nếu một lớp implements giao tiếp Serializable thì tất cả các lớp con của nó cũng sẽ được serializable. Hãy xem ví dụ dưới đây:

Person.java

package com.amzuni;
 
import java.io.Serializable;
 
class Person implements Serializable {
 
    private static final long serialVersionUID = -5917089857953402554L;
 
    private int id;
    private String name;
 
    Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
 
    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + "]";
    }
}

Engineer.java

package com.amzuni;
 
public class Engineer extends Person {
 
    private String certificate;
 
    public Engineer(int id, String name, String certificate) {
        super(id, name);
        this.certificate = certificate;
    }
 
    @Override
    public String toString() {
        return "Engineer [certificate=" + certificate + ", " + super.toString() + "]";
    }
 
}

Ví dụ sử dụng ObjectOutputStream để ghi đối tượng engineer vào file

package com.amzuni;
 
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
 
public class SerializationISExample {
    public static void main(String args[]) throws Exception {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("data/engineer.txt"));
            Engineer engineer = new Engineer(1, "Gp Coder", "Java");
            oos.writeObject(engineer);
            oos.flush();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            oos.close();
        }
        System.out.println("success...");
    }
}

Ví dụ sử dụng ObjectInputStream để đọc đối tượng engineer từ file

package com.amzuni;
 
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
 
public class DeserializationISExample {
    public static void main(String args[]) throws Exception {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("data/engineer.txt"));
            Engineer engineer = (Engineer) ois.readObject();
            System.out.println(engineer);
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            ois.close();
        }
    }
}

Thực thi chương tình SerializationISExample, sau đó thực thi chương trình DeserializationISExample. Ta có kết quả như sau:

1Engineer [certificate=Java, Person [id=1, name=Gp Coder]]

Java Serialization với sự kết hợp (Mối quan hệ HAS-A)

Nếu một lớp có một tham chiếu của một lớp khác, tất cả các tham chiếu phải được implements giao tiếp Serializable nếu không quá trình serialization sẽ không được thực hiện. Trong trường hợp đó, NotSerializableException được ném ra khi chạy.

Address.java

package com.amzuni;
 
public class Address {
    private String addressLine;
    private String city;
    private String state;
 
    public Address(String addressLine, String city, String state) {
        this.addressLine = addressLine;
        this.city = city;
        this.state = state;
    }
}

Company.java

package com.amzuni;
 
import java.io.Serializable;
 
public class Company implements Serializable {
 
    private static final long serialVersionUID = -3928260053430292056L;
 
    private int id;
    private String name;
    private Address address;// HAS-A
 
    public Company(int id, String name) {
        this.id = id;
        this.name = name;
    }
 
    public void setAddress(Address address) {
        this.address = address;
    }
}

Ví dụ sử dụng ObjectOutputStream để ghi đối tượng company vào file

package com.amzuni;
 
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
 
public class SerializationHasAExample {
    public static void main(String args[]) throws Exception {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("data/engineer.txt"));
            Company company = new Company(1, "Gp Coder");
            Address address = new Address("3/2 Street", "CT", "NK");
            company.setAddress(address);
            oos.writeObject(company);
            oos.flush();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            oos.close();
        }
        System.out.println("success...");
    }
}

Thực thi chương tình SerializationHasAExample. Ta có kết quả như sau:

java.io.NotSerializableException: com.gpcoder.serialize.Address
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at com.gpcoder.serialize.SerializationHasAExample.main(SerializationHasAExample.java:15)

Vì Address không implements giao tiếp Serializable nên bạn không thể serialize thể hiện của lớp Company.

Java Serialization với thành viên dữ liệu static

Nếu có bất kỳ thành viên dữ liệu static trong một lớp, nó sẽ không được serialized bởi vì static là một phần của lớp chứ không phải đối tượng.

package com.amzuni;
 
import java.io.Serializable;
 
public class Employee implements Serializable {
 
    private static final long serialVersionUID = 6429893828167360962L;
 
    private int id;
    private String name;
    private static String company = "com.gpcoder"; // it won't be serialized
 
    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }
 
    @Override
    public String toString() {
        return "Employee [id=" + id + ", name=" + name + "]";
    }
}

Ví dụ sử dụng ObjectOutputStream để ghi đối tượng employee vào file

package com.amzuni;
 
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
 
public class SerializationStaticExample {
    public static void main(String args[]) throws Exception {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("data/employee.txt"));
            Employee employee = new Employee(1, "Gp Coder");
            oos.writeObject(employee);
            oos.flush();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            oos.close();
        }
        System.out.println("success...");
    }
 
}

Ví dụ sử dụng ObjectInputStream để đọc đối tượng employee từ file

package com.amzuni;
 
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
 
public class DeserializationStaticExample {
    public static void main(String args[]) throws Exception {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("data/employee.txt"));
            Employee employee = (Employee) ois.readObject();
            System.out.println(employee);
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            ois.close();
        }
    }
}

Thực thi chương tình SerializationStaticExample, sau đó thực thi chương trình DeserializationStaticExample. Ta có kết quả như sau:

Kết quả không có giá trị company, do thuộc tính này là static.

Java Serialization với array hoặc collection

Trong trường hợp mảng (array) hoặc tập hợp (collection), tất cả các đối tượng của array hoặc collection phải được tuần tự hóa (Serializable). Nếu bất kỳ đối tượng không phải là serializable, thì quá trình serialization sẽ không thành công.

Externalizable trong java

Interface Externalizable cung cấp khả năng viết trạng thái của một đối tượng vào một byte stream ở định dạng nén. Nó không phải là một giao diện đánh dấu.

Interface  Externalizable cung cấp hai phương thức:

  • public void writeExternal(ObjectOutput out) throws IOException
  • public void readExternal(ObjectInput in) throws IOException

Từ khóa transient trong java

Nếu không muốn serialize bất kỳ thuộc tính nào của một lớp, bạn có thể đánh dấu nó với từ khóa transient.

Ví dụ, khai báo một lớp Student, có ba thành viên dữ liệu là id, name và age. Lớp Student implements giao tiếp Serializable, tất cả các giá trị sẽ được tuần tự nhưng nếu bạn không muốn tuần tự cho một giá trị, ví dụ: age thì bạn có thể khai báo age với từ khóa transient.

package com.amzuni;
 
import java.io.Serializable;
 
public class Student implements Serializable {
 
    private static final long serialVersionUID = -266706354210367639L;
 
    private int id;
    private String name;
    private transient int age;
 
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
 
    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + ", age=" + age + "]";
    }
}

Ví dụ sử dụng ObjectOutputStream để ghi đối tượng student vào file

package com.amzuni;
 
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
 
public class SerializationTransientExample {
    public static void main(String args[]) throws Exception {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("data/student.txt"));
            Student student = new Student(1, "gpcoder.com", 28);
            oos.writeObject(student);
            oos.flush();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            oos.close();
        }
        System.out.println("success...");
    }
 
}

Ví dụ sử dụng ObjectInputStream để đọc đối tượng student từ file

package com.amzuni;
 
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
 
public class DeserializationTransientExample {
    public static void main(String args[]) throws Exception {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("data/student.txt"));
            Student student = (Student) ois.readObject();
            System.out.println(student);
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            ois.close();
        }
 
    }
 
}

Thực thi chương tình SerializationTransientExample, sau đó thực thi chương trình DeserializationTransientExample. Ta có kết quả như sau:

1Student [id=1, name=gpcoder.com, age=0]

Kết quả không có giá trị age, do thuộc tính này là transient.