Java – Serialization

Java Serialization cho phép ghi các Java Object vào hệ thống file cho lưu trữ dài hạn hoặc trên mạng để truyền cho các ứng dụng khác. Serialization được thực thi với Serialzable interface. Serialzable interface đảm bảo khả năng sắp xếp theo tuần tự các Object. Interface này cũng gợi ý cho chúng ta sử dụng serialVersioUID

Những thay đổi Serialization không tương thích

Những thay đổi không tương thích cho các class là những thay đổi giúp đảm bảo về khả năng tương tác khó kiểm soát. Những thay đổi không tương thích có thể xảy ra khi phát triển một class như bên dưới đây.

  1. Xoá các trường – Nếu một trường bị xoá trong một class thì stream được ghi sẽ không chứa giá trị của nó. Khi stream được đọc bởi class trước đấy, gía trị của trường sẽ gán một giá trị mặc định bởi vì không có giá trị tồn tại trong stream. Tuy nhiên giá trị mặc định này có thể làm giảm khả năng của phiên bản trước đấy khi thực hiện giao kèo.
  2. Di chuyển các class lên hoặc xuống hệ thống cấp bậc – Điều này không được cho phép bởi vì dữ liệu trong stream xuất hiện sai trình tự.
  3. Thay đổi một trường không phải static sang static hoặc không phải transient sang transient – Khi dựa trên Serialization mặc định, nhưng thay đổi này là tương đương với việc xoá một trường trong class. Phiên bản này của class  sẽ không ghi dữ liệu đó vào stream, nên nó sẽ không sẵn sàng được đọc cho các phiên bản trước của class. Như là khi xoá một trường, trường của phiên bản trước sẽ được khởi tạo bằng giá trị mặc định, đó là nguyên nhân class bị lỗi không mong muốn.
  4. Thay đổi kiểu khai báo của trường cơ bản – Mỗi phiên bản của class ghi dữ liệu với kiểu khai báo. Các phiên bản trước đây của class thử đọc trường sẽ lỗi bởi vì kiểu trường dữ liệu trong stream là không giống với kiểu hiện tại.
  5. Thay đổi phương thức writeObject hoặc readObject nên nó không còn đọc và ghi dữ liệu trường mặc định hoặc thay đổi nó để cố ghi hoặc đọc khi phiên bản trước không có. Dữ liệu trường mặc định phải nhất quán xuất hiện hoặc không xuất hiện trong stream
  6. Đổi một class từ Serializable sang Extenalizable hoặc ngược lại – là một thay đổi không tương thích bởi vì stream sẽ chứa dữ liệu mà không tương thích với phần thực thi của class hiện có.
  7. Thay đổi class không phải kiểu enum sang kiểu enum hoặc ngược lại – bởi vì stream sẽ chứa dữ liệu mà không tương thích với phần thực thi của class hiện có.
  8. Loại bỏ Serializable hoặc Extenalizable – là một thay đổi không tương thích bởi vì khi được viết nó sẽ không còn cung cấp các trường cần thiết cho phiên bản cũ của class
  9. Thêm các phương thức writeReplace hoặc readResolve vào class – không tương thích nếu hành vi sẽ tạo ra một Object không tương thích với bất kỳ class cũ nào

Những thay đổi Serialization tương thích

  1. Thêm trường – Khi class đang được tổ chức lại có một trường không tồn tại trong stream, trường đó trong stream sẽ được khởi tạo giá trị mặc định cho kiểu của nó. Nếu cần khởi tạo class cụ thể, class có thể cung cấp phương thức readObject giúp khởi tạo trường thành gía trị không mặc định.
  2. Thêm các class – Stream sẽ chứa kiểu phân cấp của từng object trong stream. So sánh cấp bậc này trong stream với class hiện tại để có thể tìm ra các class thêm vào. Do không có thông tin nào trong stream để khởi tạo object, các trường class sẽ được khởi tạo thành giá trị mặc định
  3. Huỷ các class – So sánh hệ thống phân cấp class với class hiện tại có thể tìm ra class đã bị xoá. Trong trường hợp này, các trường và các Object tương ứng với class được đọc từ stream. Các trường kiểu cơ bản được bỏ đi, nhưng các object được tham chiếu bởi bởi class bị xoá được tạo, vì chúng có thể được đề cập sau này trong stream. Chúng sẽ được thu gom rác khi stream được thu gom rác hoặc thiết lập lại
  4. Thêm các phương thức writeObject/readObject – Nếu phiên bản đọc stream có những phương thức này thì readObject được kỳ vọng như thường lệ để đọc dữ liệu cần thiết được ghi vào stream sử dụng serailization mặc định. Nó nên gọi defaultReadObject đầu tiên trước khi đọc bất kỳ kiểu dữ liệu bặt buộc nào. Phương thức writeObject  thường được mong đợi để gọi defaultWriteObject để ghi dữ liệu cần thiết và sau đó có thể ghi dữ liệu không bắt buộc.
  5. Loại bỏ các phương thức writeObject/readObject – Nếu class đọc stream không có những phương thức này, dữ liệu cần thiết được đọc sử dụng serialization mặc định và dữ liệu không bắt buộc sẽ bị bỏ đi
  6. Thêm java.io.Serializable – Điều này là tương đương với việc thêm kiểu. Sẽ không có giá trị trong stream cho class này nên các trường của nó sẽ được khởi tạo giá trị mặc định. Việc hỗ trợ cho các class phân lớp không tuần tự yêu cầu kiểu của class super không có tham số constructor và bản thân class sẽ được khởi tạo giá trị mặc định. Trường hợp constructor có tham số, InvalidClassException sẽ được đưa ra.
  7. Thay đổi truy cập đến trường – Phạm vi truy cập dữ liệu public, package, protected, và private không ảnh hưởng đến khả năng Serialization để gán giá trị cho các trường
  8. Thay đổi một trường từ static sang không static hoặc transient sang không transient– Khi dựa trên Serialization mặc định để tính toán các trường sắp xếp theo tuần tự, những thay đổi này tương đương với việc thêm trường vào class. Trường mới sẽ được ghi vào stream, nhưng các class trước đấy sẽ bỏ qua giá trị vì thế Serialization sẽ không gán giá trị cho các trường static hoặc transient.

serialVersionUID

serialVersionUID là một định danh phiên bản phổ quát cho một class Serializable. Deserialization sử dụng số này để đảm bảo rằng class tải xuống tương ứng chính xác với một object được Serialzation. Nếu không khớp thì InvalidClassException sẽ được đưa ra

  1. Luôn bao gồm nó như một trường, ví dụ private static final long serialVersionUID = 7526472295622776147L bao gồm thêm trường này trong phiên bản đầu tiên của class, như một lời nhắc nhở cho tầm quan trọng của nó
  2. Không thay đổi giá trị của trường này trong các phiên bản tiếp theo, trừ khi bạn cố ý thay đổi nó cho class làm cho nó không tương thích với các object phiên bản cũ.

Các phương thức readObject và writeObject

  1. Deserialization phải được xử lý giống như constructor: Kiểm tra trạng thái của object cuối cùng cần thực hiện. Điều này nghĩa rằng readObject nên luôn luôn được thực thi trong các class Serialization, như vậy kiểm tra ở bên trên sẽ được thực hiện.
  2. Nếu constructor tạo ra các bản sao để bảo vệ cho các trường object mutable, nên nó phải là readObject

Best practice

  1. Sử dụng javadoc’s @serialtag để chỉ rõ các trường Serializable
  2. Đuôi .ser thường được sử dụng cho các file miêu tả các Object Serializable
  3. Mặc định Serialization không static hoặc transient
  4. Các class cần mở rộng không nên là Serializable trừ khi cần thiết

Ví dụ

package com.example.serialization;

import java.io.Serializable;

public class Employee implements Serializable {

//  private static final long serialVersionUID = -6470090944414208496L;
	
  private String name;
  private int id;
  transient private int salary;
	
  @Override
  public String toString(){
    return "Employee{name=" + name + ",id=" + id +", salary=" + salary + "}";
  }
	
  // phương thức getter và setter
  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public int getSalary() {
    return salary;
  }

  public void setSalary(int salary) {
    this.salary = salary;
  }	
}

Giả sử chúng ta cần ghi các object vào file và sau đó deserialize trong cùng file đó. Chúng ta sẽ cần các phương thức Util sử dụng các phương thức ObjectInputStream và ObjectOutputStream  cho mục đích deserialize

package com.example.serialization;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializationUtil {

  // deserialize to Object from given file
  public static Object deserialize(String fileName) throws IOException,
    ClassNotFoundException {
    FileInputStream fis = new FileInputStream(fileName);
    ObjectInputStream ois = new ObjectInputStream(fis);
    Object obj = ois.readObject();
    ois.close();
    return obj;
  }

Chú ý rằng, các tham số phương thức hoạt động với Object đó là base class của bất kỳ Object Java nào. Nó được ghi theo cách này để dùng chung

Tiếp theo sẽ viết thử chương trình test để chạy đoạn code bên trên

package com.example.serialization;

import java.io.IOException;

public class SerializationTest {
	
  public static void main(String[] args) {
    String fileName="employee.ser";
    Employee emp = new Employee();
    emp.setId(100);
    emp.setName("Pankaj");
    emp.setSalary(5000);
		
    //serialize to file
    try {
      SerializationUtil.serialize(emp, fileName);
    } catch (IOException e) {
      e.printStackTrace();
      return;
    }
		
    Employee empNew = null;
    try {
      empNew = (Employee) SerializationUtil.deserialize(fileName);
    } catch (ClassNotFoundException | IOException e) {
      e.printStackTrace();
    }
		
    System.out.println("emp Object::"+emp);
    System.out.println("empNew Object::"+empNew);
  }
}

Kết quả

emp Object::Employee{name=Pankaj,id=100,salary=5000}
empNew Object::Employee{name=Pankaj,id=100,salary=0}

 

 

 

You May Also Like

About the Author: Nguyen Dinh Thuc

Leave a Reply

Your email address will not be published.