serialVersionUID là gì?

SerialVersionUID là một thuộc tính định danh dùng trong serialize/deserialize một object của một class implement Serializable interface.

Trong bài viết này chúng ta sẽ bàn luận về cách sử dụng serialVersionUID và đi qua các ví dụ cụ thể để có thể hiểu rõ hơn về nó.

Serial Version UID

Nói một cách đơn giản, chúng ta sử dụng thuộc tính serialVersionUID để ghi nhớ các phiên bản của một Serializable class (class implement Serializable interface) để xác minh một object được tuần tự hoá trước đó có tương thích với Serializable class ở phiên bản hiện tại hay không.

Các thuộc tính serialVersionUID của các class khác nhau độc lập, không liên qua đến nhau. Do đó, các class khác nhau có thể có cùng giá trị serialVersionUID.

Để hiểu rõ hơn, chúng ta sẽ lấy một ví dụ về AppleProduct sử dụng thuộc tính serialVersionUID để định danh và kiểm tra sự tương thích với các object được tuần tự hoá.

public class AppleProduct implements Serializable {

    private static final long serialVersionUID = 1234567L;

    public String headphonePort;
    public String thunderboltPort;
}

Tiếp theo, định nghĩa các util class, trong đó một class dùng để tuần tự hoá AppleProduct object sang String và một class dùng để giải mã String sang AppleProduct object.

public class SerializationUtility {

    public static void main(String[] args) {
        AppleProduct macBook = new AppleProduct();
        macBook.headphonePort = "headphonePort2020";
        macBook.thunderboltPort = "thunderboltPort2020";

        String serializedObj = serializeObjectToString(macBook);
 
        System.out.println("Serialized AppleProduct object to string:");
        System.out.println(serializedObj);
    }

    public static String serializeObjectToString(Serializable o) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(o);
        oos.close();
        
        return Base64.getEncoder().encodeToString(baos.toByteArray());
    }
}

Sau khi chạy SerializationUtility.java, chúng ta nhận được một String là kết quả của việc tuần tự hoá AppleProduct object.

Serialized AppleProduct object to string:
rO0ABXNyACljb20uYmFlbGR1bmcuZGVzZXJpYWxpemF0aW9uLkFwcGxlUHJvZHVjdAAAAAAAEta
HAgADTAANaGVhZHBob25lUG9ydHQAEkxqYXZhL2xhbmcvU3RyaW5nO0wADmxpZ2h0ZW5pbmdQb3
J0cQB+AAFMAA90aHVuZGVyYm9sdFBvcnRxAH4AAXhwdAARaGVhZHBob25lUG9ydDIwMjBwdAATd
Gh1bmRlcmJvbHRQb3J0MjAyMA== (1)

Sau đó sử dụng String(1) từ kết quả trên làm tham số đầu vào cho DeserializationUtility class, chúng ta sẽ giải mã được String sang AppleProduct object như ban đầu

public class DeserializationUtility {
 
    public static void main(String[] args) {
 
        String serializedObj = (1)
        System.out.println(
          "Deserializing AppleProduct...");
 
        AppleProduct deserializedObj = (AppleProduct) deSerializeObjectFromString(
          serializedObj);
 
        System.out.println(
          "Headphone port of AppleProduct:"
            + deserializedObj.getHeadphonePort());
        System.out.println(
          "Thunderbolt port of AppleProduct:"
           + deserializedObj.getThunderboltPort());
    }
 
    public static Object deSerializeObjectFromString(String s)
      throws IOException, ClassNotFoundException {
  
        byte[] data = Base64.getDecoder().decode(s);
        ObjectInputStream ois = new ObjectInputStream(
          new ByteArrayInputStream(data));
        Object o = ois.readObject();
        ois.close();
        return o;
    }
}

Output

Deserializing AppleProduct...
Headphone port of AppleProduct:headphonePort2020
Thunderbolt port of AppleProduct:thunderboltPort2020

Bây giờ, thử chỉnh sửa serialVersionUID trong AppleProduct class thành một giá trị khác và giải mã lại bằng cách chạy lại DeserializationUtility class.

Deserializing AppleProduct...
Exception in thread "main" java.io.InvalidClassException: com.baeldung.deserialization.AppleProduct; local class incompatible: stream classdesc serialVersionUID = 1234567, local class serialVersionUID = 7654321
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
	at com.baeldung.deserialization.DeserializationUtility.deSerializeObjectFromString(DeserializationUtility.java:24)
	at com.baeldung.deserialization.DeserializationUtility.main(DeserializationUtility.java:15)

Như vậy, với việc chúng ta chỉnh sửa giá trị của serialVersionUID, kết quả của String được tuần tự hoá trước với giá trị serialVersionUID cũ sẽ không còn tương thích với AppleProduct hiện tại với serialVersionUID đã được thay đổi.

Nếu serialVersionUID không được chỉ định trong các Serializable class, JVM sẽ tự động tạo ra một và đặt cho class. Tuy nhiên, chúng ta nên cung cấp giá trị serialVersionUID khi triển khai các Serializable class và cập nhật giá trị serialVersionUID khi có những thay đổi lớn trên class.

Các thay đổi tương thích

Việc chỉnh sửa các Serializable class đồng nghĩa với cấu trúc của nó ở hiện tại sẽ khác với quá khứ. Do vậy các object được tuần tự hoá trước đó sẽ không thể giải mã lại với cấu trúc mới này vì chúng hoàn toàn khác nhau.

Tuy nhiên, vẫn có những trường hợp chúng ta thay đổi cấu trúc của Serializable class mà chúng vẫn tương thích với cấu trúc cũ.

Giả sử chúng ta thêm một thuộc tính mới lightningPort vào ProductApple class.

public class AppleProduct implements Serializable {
//...
    public String lightningPort;
}

Vì chúng ta chỉ thêm một thuộc tính mới vào AppleProduct class, do vậy không bắt buộc chúng ta phải cập nhật giá trị của serialVersionUID. Vì trong quá trình giải mã các object được tuần tự hoá trước đó, thuộc tính lightningPort sẽ không có giá trị và đơn giản là gán giá trị null cho nó, còn lại các thuộc tính khác vẫn có giá trị tương ứng.

Giá trị serialVersionUID mặc định

Nếu chúng ta không cung cấp giá trị serialVersionUID cho một Serializable class. JVM sẽ tự định nghĩa giá trị này thông qua các đặc điểm của class như tên và kiểu dữ liệu của các thuộc tính,tên class, v.v.

Giả sử chúng ta có DefaultSerial không cung cấp giá trị cho serialVersionUID.

public class DefaultSerial implements Serializable {
}

Khi tuần tự hoá một DefaultSerial object

DefaultSerial instance = new DefaultSerial();
System.out.println(SerializationUtility.serializeObjectToString(instance));

Output

rO0ABXNyACpjb20uYmFlbGR1bmcuZGVzZXJpYWxpemF0aW9uLkRlZmF1bHRTZXJpYWx9iVz3Lz/mdAIAAHhw

Tuy nhiên, khi chúng ta chỉnh sửa trên class này sẽ gây ra sự không tương thích không đáng có. Ví dụ chúng ta thêm thuộc tính name vào DefaultSerial class.

public class DefaultSerial implements Serializable {
    private String name;
}

Đây được xem là sự thay đổi có tương thích ngược, tất là cấu trúc hiện tại của class vẫn có thể tương thích với cấu trúc cũ. Do đó các object được tuần tự hoá bởi cấu trúc cũ hoàn toàn có thể giải mã lại với cấu trúc mới. Thế nhưng 

Exception in thread "main" java.io.InvalidClassException: 
  com.baeldung.deserialization.DefaultSerial; local class incompatible: 
  stream classdesc serialVersionUID = 9045863543269746292, 
  local class serialVersionUID = -2692722436255640434

Việc này là do JVM nhận thấy được sự thay đổi các thuộc tính bên trong DefaultSerial class nên giá trị của serialVersionUID cũng thay đổi theo.

Như vậy nếu chủ động định nghĩa serialVersionUID thì chúng ta sẽ có toàn quyền kiểm soát việc tuần tự hoá và giải mã của các Serializable class.

Nguồn

https://www.baeldung.com/java-serial-version-uid

4.2 10 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x