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

說明:本文爲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]某一個時間點的值),把數據臨時或者永久地保存到硬盤上(通常存放在文件裏);
  • 利用序列化實現遠程通信,並且在數據傳遞過程中或者使用時能夠保證數據結構或者對象狀態的完整性和可傳遞性,即在網絡上傳送對象的字節序列。

作者:忠勝

首發:「野指針」

來源:宜信技術學院

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章