Serializable詳解(1):程式碼驗證Java序列化與反序列化

宜信技術學院發表於2020-03-11

說明:本文為Serializable詳解(1),最後兩段內容在翻譯上出現歧義(暫時未翻譯),將在後續的Serializable(2)文中補充。

介紹:本文根據JDK英文文件翻譯而成,本譯文並非完全按照原文件字面文字直譯,而是結合文件內容及個人經驗翻譯成更為清晰和易於理解的文字,並附加程式碼驗證,幫助大家更好地理解Serializable。

性質:介面類

package java.io

public interface Serializable

1.1 翻譯文件

Serializability of a class is enabled by the class implementing the java.io.Serializable interface.

透過實現java.io.Serializable interface介面來序列化一個類。

Classes that do not implement this interface will not have any of their state serialized or deserialized.

沒有實現此介面的類任何狀態都不會序列化或反序列化。

All subtypes of a serializable class are themselves serializable.

可序列化類的所有子類而本身都是可序列化的。

The serialization interface has no methods or fields and serves only to identify the semantics of being serializable.

序列化介面沒有方法或欄位域,它僅用來標識可序列化的語義。

(1)To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype's public, protected, and (if accessible) package fields.  

(2)The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime.

(3)During deserialization, the fields of non-serializable classes will be initialized using the public or protected no-arg constructor of the class. A no-arg constructor must be accessible to the subclass that is serializable. The fields of serializable subclasses will be restored from the stream.

(1)為了讓非序列化類的子類可以被序列化,這個子類可以承擔儲存和恢復超類的pulic,protected,package欄位(如果可訪問的話)。

(2)只有當它擴充的類具有可訪問無參建構函式來初始化類的狀態時,子類才可以承擔這樣的責任。如果不是這種情況,就不能宣告一個類是可序列化的。這個錯誤將在執行的時候被檢測出來。

(3)在反序列化期間,非序列化類的欄位將透過類的以public或者protected修飾的空參建構函式例項化。無引數建構函式必須可訪問可序列化的子類。序列化子類的欄位能夠從字元流裡被還原。

1.2 輔助理解

(1)(2)(3)三塊主要說了三件事:

  • 非序列化的父類,其子類實現序列化時承擔儲存和恢復父類public、protected、package等子類可訪問到子類的欄位;
  • 非序列化的父類,其子類進行序列化時,父類需要有用public或者protected修飾的空參建構函式;
  • 若無空參建構函式的父類,其子類在執行序列化時將正常進行,但反序列化時會發生錯誤,並丟擲異常。但父類有空參建構函式,子類完成序列化,父類屬性卻沒有參與到序列化中。

1.3 注意:此處有三個坑。

  • (1)中所述父類未實現序列化,實現序列化的子類會承擔儲存和恢復父類的public、protected、package等子類可訪問到子類的欄位。此處我個人理解為實現序列化的子類進行序列化的時候繼承了未實現序列化的父類中子類可訪問到的屬性,但序列化時無法記錄下父類物件的狀態資訊;
  • 此處文件若要正確讀取理解,切記(1)(2)(3)不可拆分,要放在一起去理解(上文之所以分開是便於翻譯);
  • 此處英文翻譯成漢字,難以理解其真實含義,所以透過下面的程式碼驗證來輔助理解

1.4 程式碼驗證

輔以A/B兩套型別程式碼對比理解:

1)A套

父類:Biology 類

package com.springboot.SpringBootDemo.serializable;
    
            public class Biology {
                
                public String type;
                
                private int num;
            
                public Biology(String type, int num) {
                    this.type = type;
                    this.num = num;
                }
            
                public String getType() {
                    return type;
                }
            
                public void setType(String type) {
                    this.type = type;
                }
            
                public int getNum() {
                    return num;
                }
            
                public void setNum(int num) {
                    this.num = num;
                }
            }

子類:People 類

package com.springboot.SpringBootDemo.serializable;
    
            import java.io.Serializable;
            
            public class People extends Biology implements Serializable{
            
                private static final long serialVersionUID = -6623611040000763479L;
            
                public String name;
                
                protected String gender;
                
                private int age;
            
                public People(String type, int num, String name ,String gender ,int age) {
                    super(type, num);
                    this.name = name;
                    this.gender = gender;
                    this.age = age;
                }
            
                public String getName() {
                    return name;
                }
            
                public void setName(String name) {
                    this.name = name;
                }
            
                public String getGender() {
                    return gender;
                }
            
                public void setGender(String gender) {
                    this.gender = gender;
                }
            
                public int getAge() {
                    return age;
                }
            
                public void setAge(int age) {
                    this.age = age;
                }
            }

測試類:

           import java.io.FileInputStream;
            import java.io.FileOutputStream;
            import java.io.IOException;
            import java.io.ObjectInputStream;
            import java.io.ObjectOutputStream;
            
            public class Test {
                
                public static void main(String[] args) throws IOException, ClassNotFoundException {
                    People pp = new People("human",10000,"張三","男",25);
                    
                    FileOutputStream fos = new FileOutputStream("test.txt");
                    ObjectOutputStream oos = new ObjectOutputStream(fos);
                    oos.writeObject(pp);
                    oos.flush();
                    oos.close();
                    
                    //反序列化
                    FileInputStream sfis = new FileInputStream("test.txt");
                    ObjectInputStream sois = new ObjectInputStream(sfis);
                    People p = (People) sois.readObject();
                    System.out.println(
                            p.getType() +" "+
                            p.getNum() +" "+
                            p.getName() +" "+
                            p.getGender() +" "+
                            p.getAge()
                            );
                }
            }

結果:

   Exception in thread "main" java.io.InvalidClassException: com.springboot.SpringBootDemo.serializable.People; no valid constructor
                at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(Unknown Source)
                at java.io.ObjectStreamClass.checkDeserialize(Unknown Source)
                at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
                at java.io.ObjectInputStream.readObject0(Unknown Source)
                at java.io.ObjectInputStream.readObject(Unknown Source)
                at com.springboot.SpringBootDemo.serializable.Test.main(Test.java:23)

結果說明:在序列化時未發生異常,而在反序列化readObject()時發生異常。也就是說,父類沒有無參建構函式時,序列化正常進行,但反序列化時丟擲newInvalidClassException異常。

2)B套

父類:Person類

            public class Person {
                
                public String name;
                
                public String gender;
                
                public int age;
                
                float height;
                
            
                public String getName() {
                    return name;
                }
            
                public void setName(String name) {
                    this.name = name;
                }
            
                public String getGender() {
                    return gender;
                }
            
                public void setGender(String gender) {
                    this.gender = gender;
                }
            
                public int getAge() {
                    return age;
                }
            
                public void setAge(int age) {
                    this.age = age;
                }
            
                public float getHeight() {
                    return height;
                }
            
                public void setHeight(float height) {
                    this.height = height;
                }
            }

子類:Male類

        import java.io.Serializable;
        
        public class Male extends Person implements Serializable{
            /**
             * 
             */
            private static final long serialVersionUID = -7361904256653535728L;
            
            public boolean beard;
            
            protected String weight;
            
        
            public boolean havaBeard(int age){
                boolean flag = false;
                
                if(age>=18){
                    flag = true;
                }
                return flag;
            }
        
        
            public boolean isBeard() {
                return beard;
            }
        
        
            public void setBeard(boolean beard) {
                this.beard = beard;
            }
        
        
            public String getWeight() {
                return weight;
            }
        
        
            public void setWeight(String weight) {
                this.weight = weight;
            }
            
        }

測試類:

        import java.io.FileInputStream;
        import java.io.FileOutputStream;
        import java.io.IOException;
        import java.io.ObjectInputStream;
        import java.io.ObjectOutputStream;
        public class SubTypeSerializable {
        
            public static void main(String[] args) throws IOException, ClassNotFoundException{
                
                /**Male繼承父類Person,自身實現序列化介面,其父類Person沒有實現序列化介面*/
                FileOutputStream fos = new FileOutputStream("male.txt");
                ObjectOutputStream oos = new ObjectOutputStream(fos);
                Male male = new Male();
                /**
                 * 其父類的父類Person的屬性
                 * 
                 * public String name;
                   public String gender;
                   public int age;
                   float height;
                 * */
                male.setName("張三");
                male.setGender("男性");
                male.setAge(25);
                male.setHeight(175);
                /**
                 * 其自身屬性
                 * public boolean beard;
                 * */
                male.setBeard(true);
                oos.writeObject(male);
                oos.flush();
                oos.close();
                
                //反序列化
                FileInputStream fis = new FileInputStream("male.txt");
                ObjectInputStream ois = new ObjectInputStream(fis);
                Male ml = (Male) ois.readObject();
                System.out.println(ml.getName() +" "+ml.getGender()+" "+ml.getHeight() +" "+ml.getAge()+" "+male.isBeard());
            }
    }

結果:

    ml.getName() == null 
    ml.getGender() == null 
    ml.getHeight() == 0.0 
    ml.getAge() == 0 
    male.isBeard() == true

1.5 測試分析

1)父類屬性

public String name;

public String gender;

public int age;

float height;

其狀態資訊均未被記錄;

2)自身屬性

public boolean beard;

其狀態資訊被記錄

2.1 翻譯文件

When traversing a graph, an object may be encountered that does not support the Serializable interface. In this case the NotSerializableException will be thrown and will identify the class of the non-serializable object.

當迴圈遍歷一個資料結構圖(資料結構圖可理解為資料結構型別,比如二叉樹)的時候,物件可能會遭遇到不支援實現序列化介面的情景。在這種情況下,將發生丟擲NotSerializableException異常,並且該類被定義為不可序列化類。

Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures:

在實現序列化和反序列化過程中,特殊處理的類需要實現這些特殊的方法:

   private void writeObject(java.io.ObjectOutputStream out) throws IOException
   private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException; 
                           
   private void readObjectNoData() throws ObjectStreamException;

The writeObject method is responsible for writing the state of the object for its particular class so that the corresponding readObject method can restore it. The default mechanism for saving the Object's fields can be invoked by calling out.defaultWriteObject. The method does not need to concern itself with the state belonging to its superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the writeObject method or by using the methods for primitive data types supported by DataOutput.

witeObject方法負責寫入特定類的Object物件狀態資訊,readObject方法可以還原該Object物件的狀態資訊。儲存該Object物件欄位的預設機制是透過呼叫out.defaultWriteObject來實現。該方法不需要關注屬於其超類或子類的狀態。透過使用writeObject方法將各個欄位寫入ObjectOutputStream,或使用DataOutput支援的基本資料型別的方法來儲存狀態。

The readObject method is responsible for reading from the stream and restoring the classes fields. It may call in.defaultReadObject to invoke the default mechanism for restoring the object's non-static and non-transient fields. The defaultReadObject method uses information in the stream to assign the fields of the object saved in the stream with the correspondingly named fields in the current object. This handles the case when the class has evolved to add new fields. The method does not need to concern itself with the state belonging to its superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the writeObject method or by using the methods for primitive data types supported by DataOutput.

readObject方法是負責讀取資料流並恢復該類的欄位。它可以透過呼叫in.defaultReadObject來恢復非static以及非transient修飾的欄位。defaultReadObject方法透過資料流中的資訊,把當前類儲存在資料流中的欄位資訊分配到相對應的欄位名(也就是說把欄位的值分配給相對應的欄位名)。這種處理方式也能處理該類新增欄位的情況。該方法不需要關注屬於其超類或子類的狀態。透過使用writeObject方法將各個欄位寫入ObjectOutputStream,或使用DataOutput支援的基本資料型別的方法來儲存狀態。透過writeObject方法把物件Object的各個欄位寫入到ObjectOutputStream中,或者透過使用DataOutput支援的基本資料型別的方法來儲存該Object物件的狀態資訊。

The readObjectNoData method is responsible for initializing the state of the object for its particular class in the event that the serialization stream does not list the given class as a superclass of the object being deserialized. This may occur in cases where the receiving party uses a different version of the deserialized instance's class than the sending party, and the receiver's version extends classes that are not extended by the sender's version. This may also occur if the serialization stream has been tampered; hence, readObjectNoData is useful for initializing deserialized objects properly despite a "hostile" or incomplete source stream.

(該處翻譯有些吃力,所以直接軟體翻譯,會後續進行程式碼驗證體悟)

當出現反序列化與序列化類的版本不一致的情況時,readObjectNoData()標籤方法負責初始化物件的欄位值。這種情況可能發生在反序列化時,接收方使用了傳送方物件的類的不同版本,或者接收方繼承的類的版本與傳送方繼承的類的版本不一致。另外,當序列化流被篡改了,也會發生這種情況。因此,當出現類不一致或者反序列化流不完全的情況時,readObjectNoData初始化反序列化物件的欄位就非常有用了。

2.2 程式碼驗證

1)改變之前

    public class Cat implements Serializable{
        /**
         * 
         */
        private static final long serialVersionUID = -5731096200028489933L;
        
        
        public String color;
        
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    }

改變之前測試類:

    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    public class CatFamilylTest {
        
        public static void main(String[] args) throws Exception {
            serializable();
            deSerializable();
        }
        
        public static void serializable() throws Exception{
            Cat cat = new Cat();
            cat.setColor("white");
            FileOutputStream fos = new FileOutputStream("catFamily.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(cat);
            oos.flush();
            oos.close();
        }
        
        public static void deSerializable() throws Exception{
            FileInputStream sfis = new FileInputStream("catFamily.txt");
            ObjectInputStream sois = new ObjectInputStream(sfis);
            Cat cat = (Cat) sois.readObject();
            System.out.println(cat.getColor());
        }
    }

結果:white

2)第一次改變

第一次改變之增加父類:

    import java.io.Serializable;
    public class CatFamily implements Serializable{
        
        /**
         * 
         */
        private static final long serialVersionUID = -7796480232179180594L;
        public String catType;
        
    
        public String getCatType() {
            return catType;
        }
    
        public void setCatType(String catType) {
            this.catType = catType;
        }
        
    
        private void readObjectNoData() {
            this.catType = "tiger";                 
        } 
    }

第一次改變之後之Cat類變化:

    public class Cat extends CatFamily{
        /**
         * 
         */
        private static final long serialVersionUID = -5731096200028489933L;
        
        
        public String color;
        
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    }

第一次改變之讀取已經存在的catFamily.txt檔案:

    public class CatFamilylTest {
    
        public static void main(String[] args) throws Exception {
            deSerializable();
        }
        public static void deSerializable() throws Exception{
            FileInputStream sfis = new FileInputStream("catFamily.txt");
            ObjectInputStream sois = new ObjectInputStream(sfis);
            Cat cat = (Cat) sois.readObject();
            System.out.println(cat.getColor()+" <---->"+cat.getCatType());
        }
    }

第一次改變之結果:white <---->tiger

3)第二次改變測試

第二次改變之父類:

    public class CatFamily{
    
        public String catType;
        
    
        public String getCatType() {
            return catType;
        }
    
        public void setCatType(String catType) {
            this.catType = catType;
        }
        
    
        private void readObjectNoData() {
            this.catType = "tiger";                 
        } 
    }

第二次改變之Cat類:

    import java.io.Serializable;
    public class Cat extends CatFamily implements Serializable{
        /**
         * 
         */
        private static final long serialVersionUID = -5731096200028489933L;
        
        
        public String color;
        
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    }

第二次改變之測試類:

    public class CatFamilylTest {
    
        public static void main(String[] args) throws Exception {
            deSerializable();
            
        }
        public static void deSerializable() throws Exception{
            FileInputStream sfis = new FileInputStream("catFamily.txt");
            ObjectInputStream sois = new ObjectInputStream(sfis);
            Cat cat = (Cat) sois.readObject();
            System.out.println(cat.getColor()+" <---->"+cat.getCatType());
        }
    }

第二次改變之結果:white <---->null

4)第三次改變舉例對比驗證

第三次改變之拋棄父類,且Cat類改變:

    import java.io.Serializable;
    public class Cat implements Serializable{
        /**
         * 
         */
        private static final long serialVersionUID = -5731096200028489933L;
        
        public String type;
        
        public String color;
        
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
        
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    
        private void readObjectNoData() {
            this.type = "hellokitty";                 
        }
    }

第三次改變之測試類:

    public class CatFamilylTest {
    
        public static void main(String[] args) throws Exception {
            deSerializable();
        }
        
        public static void deSerializable() throws Exception{
            FileInputStream sfis = new FileInputStream("catFamily.txt");
            ObjectInputStream sois = new ObjectInputStream(sfis);
            Cat cat = (Cat) sois.readObject();
            System.out.println(cat.getColor()+" <---->"+cat.getType());
        }
    }

第三次改變之測試結果:white <---->null

2.3 測試程式碼描述

1)第一種(改變之前)

描述:建立實現序列化介面的Cat類,以及對應的測試類生產檔案catFamily.txt

兩個目的:

  • 用於建立變化的基礎層程式碼;
  • 生成序列化後的檔案。

結果:反序列化catFamily.txt檔案,得出正常結果 write 。

2)第二種(第一次改變對比未改變)

改變之處:

  • 增加實現序列化介面的父類CatFamily類,增添readObjectNoData()方法,並且設定屬性欄位catType值為tiger;
  • Cat類不直接實現序列化Serializable介面,而是繼承CatFamily類;
  • 測試類對catFamily.txt進行反序列化讀取。

目的:驗證readObjectNoData()標籤方法結果。

結果:反序列化catFamily.txt檔案,得出結果 white <---->tiger。

總結:實現readObjectNoData()標籤方法。

3)第三種(第二次改變對比第一次改變)

改變之處:

  • 改變父類CatFamily類,去掉實現序列化Serializable介面;
  • 子類Cat類依然繼承父類CatFamily類,並且直接實現序列化Serializable介面;
  • 測試類對catFamily.txt進行反序列化讀取。

目的:驗證父類未實現序列化Serializable介面時,readObjectNoData()方法是否繼續有效。

結果:反序列化catFamily.txt檔案,得出結果 white <---->null 。

總結:readObjectNoData()方法沒有得到體現。

4)第四種(第三次改變對比未改變)

改變之處:

  • Cat類去掉父類CatFamily類,自身直接實現序列化Serializable介面;
  • Cat類實現readObjectNoData()方法;
  • 測試類對catFamily.txt進行反序列化讀取。

目的:測試readObjectNoData()方法的作用域。

結果:反序列化catFamily.txt檔案,得出結果 white <---->null。

總結:readObjectNoData()方法作用域為寫入catFamily.txt檔案的物件Object的實體類的實現序列化Serializable介面的父類。

2.4 推測總結:

  • readObjectNoData()標籤方法作用域為進行序列化物件的父類,並且其父類必須實現了序列化介面Serializable;
  • readObjectNoData()標籤方法在上面測試的程式碼中體現作用類似於set屬性;
  • readObjectNoData()標籤方法內set的屬性值為該類的屬性值,也就是說當引用其他物件屬性值進行set時,該方法是無效的。

3.1 翻譯文件

Serializable classes that need to designate an alternative object to be used when writing an object to the stream should implement this special method with the exact signature:ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

實現序列化的類,其Object物件被指定另外一個實現序列化非此類的物件進行替換的時候,在進行把該實體Object物件寫入到資料流中時,需要實現Object writeReplace() throws ObjectStreamException;這個特殊的標籤方法。

3.2 程式碼驗證

注意:替換類和被替換類都需要實現序列化介面,否則在寫入(writeObject)時會丟擲java.io.NotSerializableException異常,且被替換類為徹底被替換。

1)測試writeReplace()標籤方法

實體類:

    import java.io.ObjectStreamException;
    import java.io.Serializable;
    
    public class Dog implements Serializable{
        /**
         * 
         */
        private static final long serialVersionUID = -4094903168892128473L;
        
        private String type;
        
        private String color;
    
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
        private Object writeReplace() throws ObjectStreamException {
            Wolf wolf = new Wolf();
            wolf.setType(type);
            wolf.setColor(color);
            return wolf;
        }
    }
    
    class Wolf implements Serializable{
        /**
         * 
         */
        private static final long serialVersionUID = -1501152003733531169L;
    
        private String type;
        
        private String color;
    
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    }

測試類:

    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    public class DogTest {
        
        public static void serializable() throws IOException{
            Dog dog = new Dog();
            dog.setColor("white");
            dog.setType("Chinese garden dog");
            FileOutputStream fos = new FileOutputStream("dog.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(dog);
            oos.flush();
            oos.close();
        }
        
        public static void deSerializable() throws IOException,ClassNotFoundException{
            FileInputStream sfis = new FileInputStream("dog.txt");
            ObjectInputStream sois = new ObjectInputStream(sfis);
            Wolf wolf = (Wolf) sois.readObject();
            System.out.println(wolf.getType() +"<------->"+ wolf.getColor());
        }
        
        public static void main(String[] args) throws IOException ,ClassNotFoundException{
            serializable();
            deSerializable();
        }
    }

程式碼實現結果:Chinese garden dog<------->white。

2)測試是否被徹底替換

程式碼說明:實體類不修改,只修改測試類的反序列化方法,在readObject()方法時由Wolf物件轉變為Dog物件。

    public static void deSerializable() throws IOException,ClassNotFoundException{
        FileInputStream sfis = new FileInputStream("dog.txt");
        ObjectInputStream sois = new ObjectInputStream(sfis);
        Dog dog = (Dog) sois.readObject();
        System.out.println(dog.getType() +"<------->"+ dog.getColor());
    }

程式碼實現結果:

        (
          第25行:Dog dog = (Dog) sois.readObject(); 
          第32行:deSerializable();
         )
        Exception in thread "main" java.lang.ClassCastException: com.springboot.SpringBootDemo.serializable.Wolf cannot be cast to com.springboot.SpringBootDemo.serializable.Dog
    at com.springboot.SpringBootDemo.serializable.DogTest.deSerializable(DogTest.java:25)
    at com.springboot.SpringBootDemo.serializable.DogTest.main(DogTest.java:32)

序列化物件為Dog物件,而反序列化依然透過Dog物件,結果發生異常,此時可知在序列化時Dog物件被Wolf物件給替換了。

4.1 翻譯文件

This writeReplace method is invoked by serialization if the method exists and it would be accessible from a method defined within the class of the object being serialized. Thus, the method can have private,protected and package-private access. Subclass access to this method follows java accessibility rules.

在序列化物件時,其類的方法中如果有writeReplace標籤方法存在的話,則該標籤方法會在序列化寫入時被呼叫。因此該方法可以具有private,protected和package-private訪問。該類的子類訪問該方法時會遵循java可訪問性規則。

4.2 程式碼驗證

注意:

  • 父類實現writeReplace標籤方法;
  • 子類擁有訪問writeReplace標籤方法的許可權。

1)實體類

    import java.io.ObjectStreamException;
    import java.io.Serializable;
    
    public class Dog implements Serializable{
        /**
         * 
         */
        private static final long serialVersionUID = -4094903168892128473L;
        
        private String type;
        
        private String color;
    
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
        public Object writeReplace() throws ObjectStreamException {
            Wolf wolf = new Wolf();
            wolf.setType(type);
            wolf.setColor(color);
            return wolf;
        }
    }
    
    class ChineseGardenDog extends Dog {
        private float height;
    
        public float getHeight() {
            return height;
        }
    
        public void setHeight(float height) {
            this.height = height;
        }
    }
    
    
    class Wolf implements Serializable{
        
        /**
         * 
         */
        private static final long serialVersionUID = -1501152003733531169L;
    
        private String type;
        
        private String color;
    
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    }

2)測試類

    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    public class DogTest {
        
        public static void serializable() throws IOException{
            ChineseGardenDog dog = new ChineseGardenDog();
            dog.setColor("white");
            dog.setType("Chinese garden dog");
            dog.setHeight(55);
            FileOutputStream fos = new FileOutputStream("dog.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(dog);
            oos.flush();
            oos.close();
        }
        
        public static void deSerializable() throws IOException,ClassNotFoundException{
            FileInputStream sfis = new FileInputStream("dog.txt");
            ObjectInputStream sois = new ObjectInputStream(sfis);
            Wolf wolf = (Wolf) sois.readObject();
            System.out.println(wolf.getType() +"<------->"+ wolf.getColor());
        }
        
        
        public static void main(String[] args) throws IOException ,ClassNotFoundException{
            serializable();
            deSerializable();
        }
    }

測試結果:Chinese garden dog<------->white。

5.1 翻譯文件

Classes that need to designate a replacement when an instance of it is read from the stream should implement this special method with the exact signature.

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

This readResolve method follows the same invocation rules and accessibility rules as writeReplace.

當從資料流中讀取一個例項的時候,指定替換的類需要實現此特殊方法。

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

readResolve標籤方法遵循與writeReplace相同的呼叫規則和可訪問性規則。

5.2 程式碼驗證

注:該方法的寫入物件例項和讀取物件例項為同一個物件(適用於單例模式)。

1)未實現標籤方法

實體類:

        import java.io.Serializable;
        public class Mouse implements Serializable{
            /**
             * 
             */
            private static final long serialVersionUID = -8615238438948214201L;
            
            private String name;
            
            public static Mouse INSTANCE;
            
            
            public static Mouse getInstance(){
                if(INSTANCE == null){
                    INSTANCE = new Mouse();
                }
                return INSTANCE;
            }
        
            public String getName() {
                return name;
            }
        
            public void setName(String name) {
                this.name = name;
            }
        }

測試類:

        import java.io.FileInputStream;
        import java.io.FileOutputStream;
        import java.io.IOException;
        import java.io.ObjectInputStream;
        import java.io.ObjectOutputStream;
        
        public class MouseTest {
            
            public static void serializable() throws IOException{
                Mouse mouse= Mouse.getInstance();
                mouse.setName("Jerry");
                FileOutputStream fos = new FileOutputStream("mouse.txt");
                ObjectOutputStream oos = new ObjectOutputStream(fos);
                System.out.println("寫入物件hash值 = "+ mouse.hashCode());
                oos.writeObject(mouse);
                oos.flush();
                oos.close();
            }
            
            public static void deSerializable() throws IOException,ClassNotFoundException{
                FileInputStream sfis = new FileInputStream("mouse.txt");
                ObjectInputStream sois = new ObjectInputStream(sfis);
                Mouse mouse = (Mouse) sois.readObject();
                System.out.println("讀取物件hash值 = " +mouse.hashCode());
            }
            
            public static void main(String[] args) throws IOException, ClassNotFoundException {
                serializable();
                deSerializable();
            }
        }

測試結果:

寫入物件hash值 = 366712642

讀取物件hash值 = 1096979270

2)實現標籤方法:(測試類不變,實體類增加readResolve()方法)

實體類:

        import java.io.ObjectStreamException;
        import java.io.Serializable;
        
        public class Mouse implements Serializable{
            /**
             * 
             */
            private static final long serialVersionUID = -8615238438948214201L;
            
            private String name;
            
            public static Mouse INSTANCE;
            
            
            public static Mouse getInstance(){
                if(INSTANCE == null){
                    INSTANCE = new Mouse();
                }
                return INSTANCE;
            }
        
            public String getName() {
                return name;
            }
        
            public void setName(String name) {
                this.name = name;
            }
            
            private Object readResolve() throws ObjectStreamException{
                return INSTANCE;
            }
        }

測試結果:

寫入物件hash值 = 366712642

讀取物件hash值 = 366712642

推測:指定寫入的物件例項和讀取指定的物件例項為同一個。

6.1 翻譯文件

The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender's class, then deserialization will result in an {@link InvalidClassException}. A serializable class can declare its own serialVersionUID explicitly by declaring a field named   "serialVersionUID"  that must be static, final, and of type   long:  

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification. However, it is   strongly recommended  that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected   InvalidClassExceptions during deserialization. Therefore, to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value. It is also strongly advised that explicit serialVersionUID declarations use the   private  modifier where possible, since such declarations apply only to the immediately declaring class--serialVersionUID fields are not useful as inherited members. Array classes cannot declare an explicit serialVersionUID, so they always have the default computed value, but the requirement for matching serialVersionUID values is waived for array classes.

6.2 針對實現Serializable介面 程式碼驗證

父類:Person類

    public class Person {
        
        public String name;
        
        public String gender;
        
        public int age;
        
        float height;
        
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getGender() {
            return gender;
        }
    
        public void setGender(String gender) {
            this.gender = gender;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public float getHeight() {
            return height;
        }
    
        public void setHeight(float height) {
            this.height = height;
        }
    }

子類:Male類

    import java.io.Serializable;
    
    public class Male extends Person implements Serializable{
        /**
         * 
         */
        private static final long serialVersionUID = -7361904256653535728L;
        
        public boolean beard;
        
    
    
        public boolean havaBeard(int age){
            boolean flag = false;
            
            if(age>=18){
                flag = true;
            }
            return flag;
        }
    
    
    
        public boolean isBeard() {
            return beard;
        }
    
    
    
        public void setBeard(boolean beard) {
            this.beard = beard;
        }
    }

三級子類:Students類

    public class Students extends Male{
        
        private static final long serialVersionUID = -6982821977091370834L;
    
        public String stuCard;
        
        private int grades;
    
        public String getStuCard() {
            return stuCard;
        }
    
        public void setStuCard(String stuCard) {
            this.stuCard = stuCard;
        }
    
        public int getGrades() {
            return grades;
        }
    
        public void setGrades(int grades) {
            this.grades = grades;
        }
    }

類:Female類

    import java.io.Serializable;
    
    public class Female implements Serializable{
        
        private static final long serialVersionUID = 6907419491408608648L;
    
        public String name;
        
        public String gender;
        
        public int age;
        
        float height;
    
        
        
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getGender() {
            return gender;
        }
    
        public void setGender(String gender) {
            this.gender = gender;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public float getHeight() {
            return height;
        }
    
        public void setHeight(float height) {
            this.height = height;
        }
    }

測試類:SubTypeSerializable

    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    public class SubTypeSerializable {
    public static void main(String[] args) throws IOException, ClassNotFoundException{
        
        /**(一)、Person實體類,未實現序列化介面,無父類*/
        FileOutputStream fo = new FileOutputStream("person.txt");
        ObjectOutputStream ss = new ObjectOutputStream(fo);
        Person person = new Person();
        person.setAge(100);
        person.setGender("性別");
        person.setHeight(165);
        person.setName("人類");
        ss.writeObject(person);
        ss.flush();
        ss.close();
        
        //反序列化
        FileInputStream sfis = new FileInputStream("person.txt");
        ObjectInputStream sois = new ObjectInputStream(sfis);
        Person ps = (Person) sois.readObject();
        System.out.println(ps.getName() +" "+ps.getGender()+" "+ps.getHeight() +" "+ps.getAge());
    
        /**結果:
            在執行writeObject(person)是發生異常
        Exception in thread "main" java.io.NotSerializableException:
            com.springboot.SpringBootDemo.serializable.Person
            at java.io.ObjectOutputStream.writeObject0(Unknown Source)
            at java.io.ObjectOutputStream.writeObject(Unknown Source)
            at com.springboot.SpringBootDemo.serializable.SubTypeSerializable.main(SubTypeSerializable.java:21)
         Exception in thread "main" java.io.WriteAbortedException: writing aborted;    java.io.NotSerializableException: com.springboot.SpringBootDemo.serializable.Person
            at java.io.ObjectInputStream.readObject0(Unknown Source)
            at java.io.ObjectInputStream.readObject(Unknown Source)
            at com.springboot.SpringBootDemo.serializable.SubTypeSerializable.main(SubTypeSerializable.java:28)
         * */
        System.out.println("<--------------------------------------------------------------------------->");
        
    
        /**(二)、Male繼承父類Person,自身實現序列化介面,其父類Person沒有實現序列化介面*/
        FileOutputStream fos = new FileOutputStream("male.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        Male male = new Male();
        /**
         * 其父類的父類Person的屬性
         * 
         * public String name;
           public String gender;
           public int age;
           float height;
         * */
        male.setName("張三");
        male.setGender("男性");
        male.setAge(25);
        male.setHeight(175);
        /**
         * 其自身屬性
         * public boolean beard;
         * */
        male.setBeard(true);
        oos.writeObject(male);
        oos.flush();
        oos.close();
        
        //反序列化
        FileInputStream fis = new FileInputStream("male.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Male ml = (Male) ois.readObject();
        System.out.println(ml.getName() +" "+ml.getGender()+" "+ml.getHeight() +" "+ml.getAge()+" "+male.isBeard());
        
        /**結果:
         * 父類沒有被序列化,唯獨子類屬性被序列化
         * 
         * ml.getName() == null 
         * ml.getGender() == null 
         * ml.getHeight() == 0.0 
         * ml.getAge() == 0 
         * male.isBeard() == true
         * 父類屬性:
         *  public String name;
            public String gender;
            public int age;
            float height;
                       均未實現序列化;
                    自身屬性:
            public boolean beard;
                        實現序列化        
         * */
        
        System.out.println("<--------------------------------------------------------------------------->");
   
        /**(三)、Female實現序列化介面,無父類*/
        FileOutputStream ffos = new FileOutputStream("female.txt");
        ObjectOutputStream foos = new ObjectOutputStream(ffos);
        Female female = new Female();
        /**
         * 其自身的屬性
         * public String name;
           public String gender;
           public int age;
           float height;
         **/
        female.setAge(25);
        female.setGender("女性");
        female.setHeight(165);
        female.setName("張芳");
        foos.writeObject(female);
        foos.flush();
        foos.close();
        
        //反序列化
        FileInputStream ffis = new FileInputStream("female.txt");
        ObjectInputStream fois = new ObjectInputStream(ffis);
        Female fm = (Female) fois.readObject();
        System.out.println(fm.getName() +" "+fm.getGender()+" "+fm.getHeight() +" "+fm.getAge());
        
        /**結果:
         * 自身屬性均實現序列化
         * 
         * fm.getName() == 張芳
         * fm.getGender() == 女性 
         * fm.getHeight() == 165.0 
         * fm.getAge() == 25
         * 所有屬性均實現序列化*/
        System.out.println("<--------------------------------------------------------------------------->");
        
        
        /**(四)、Students未實現序列化介面,繼承父類Male,其父類繼承父類Person,自身實現序列化介面,其父類Person沒有實現序列化介面*/
        FileOutputStream stufos = new FileOutputStream("students.txt");
        ObjectOutputStream stuoos = new ObjectOutputStream(stufos);
        Students students = new Students();
        /**
         * 其父類的父類Person的屬性
         * 
         * public String name;
           public String gender;
           public int age;
           float height;
         * */
        students.setName("王小明");
        students.setGender("男性");
        students.setAge(15);
        students.setHeight(160);
        /**
         * 其父類Male屬性
         * public boolean beard;
         * */
        students.setBeard(true);
        /**
         * 自身屬性
         * public String stuCard;
           private int grades;
         * */
        students.setStuCard("1234567890987");
        students.setGrades(300);
        stuoos.writeObject(students);
        stuoos.flush();
        stuoos.close();
        
        //反序列化
        FileInputStream stufis = new FileInputStream("students.txt");
        ObjectInputStream stuois = new ObjectInputStream(stufis);
        Students st = (Students) stuois.readObject();
        System.out.println(st.getName() +" "+st.getGender()+" "+st.getAge()+" "+st.getHeight()+" "+st.isBeard()+" "+st.getStuCard()+" "+st.getGrades());
        
        /**結果:
         * 父類的父類屬性未實現序列化,父類實現序列化,自身實現序列化
         * st.getName() == null 
         * st.getGender() == null 
         * st.getAge() == 0 
         * st.getHeight() == 0.0 
         * st.isBeard() == true 
         * st.getStuCard() == 1234567890987 
         * st.getGrades() == 300
         * 自身public String stuCard;
              private int grades;
                實現序列化;
               而父類Male屬性
              public boolean beard
                實現序列化;
               父類的父類Person
            public String name;
            public String gender;
            public int age;
            float height;
                       未實現序列化
         * */
        }
    }

6.3 回顧

1)在使用ObjectInputStream、ObjectOutputStream對物件進行寫入寫出時,其寫入的物件的類需要實現java.io.Serializable序列化介面,否則會報出 writeObject()異常:

            Exception in thread "main" java.io.NotSerializableException: com.springboot.SpringBootDemo.serializable.Person
            at java.io.ObjectOutputStream.writeObject0(Unknown Source)
            at java.io.ObjectOutputStream.writeObject(Unknown Source)
            at com.springboot.SpringBootDemo.serializable.SubTypeSerializable.main(SubTypeSerializable.java:21)
            readObject()異常: 
            Exception in thread "main" java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.springboot.SpringBootDemo.serializable.Person
                java.io.ObjectInputStream.readObject0(Unknown Source)
                at java.io.ObjectInputStream.readObject(Unknown Source)
                at com.springboot.SpringBootDemo.serializable.SubTypeSerializable.main(SubTypeSerializable.java:28)

2)父類未實現java.io.Serializable序列化介面,其子類依然可以進行序列化,但其子類進行物件序列化讀寫時,父類無法被序列化,只能自身實現序列化;

3)自身實現java.io.Serializable序列化介面,在進行物件讀寫時會被實現序列化;

4)父類實現java.io.Serializable序列化介面,其子類不需要再次申明實現序列化,子類在進行物件序列化讀寫時,父類和子類均被實現序列化。

7.1 總結

1)java.io.Serializable介面

首先,Serializable類是一個介面,所以物件的序列化並不是Serializable來實現的;

其次,Serializable是一個標籤,各種序列化類在讀取到這個標籤的時候,會按照自己的方式進行序列化。

2)序列化是幹什麼的,為什麼需要序列化

我們知道,當兩個程式進行遠端通訊時,可以相互傳送各種型別的資料,包括文字、圖片、音訊、影片等,而這些資料都會以二進位制序列的形式在網路上傳送。

那麼當兩個Java程式進行通訊時,能否實現程式間的物件傳送呢?答案是可以的!如何做到呢?這就需要Java序列化與反序列化了!

換句話說:一方面,傳送方需要把這個Java物件轉換為位元組序列,然後在網路上傳送;另一方面,接收方需要從位元組序列中恢復出Java物件。

當我們明晰了為什麼需要Java序列化和反序列化後,我們很自然地會想Java序列化的好處。

  • 實現了資料的持久化,透過序列化可以記錄下資料結構或者物件的狀態(也就是實體變數Object[注:不是Class]某一個時間點的值),把資料臨時或者永久地儲存到硬碟上(通常存放在檔案裡);
  • 利用序列化實現遠端通訊,並且在資料傳遞過程中或者使用時能夠保證資料結構或者物件狀態的完整性和可傳遞性,即在網路上傳送物件的位元組序列。

作者:忠勝

首發:「野指標」

來源:宜信技術學院


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69918724/viewspace-2679583/,如需轉載,請註明出處,否則將追究法律責任。

相關文章