天天看點

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接口;

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

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

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

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

  • Cat類去掉父類CatFamily類,自身直接實作序列化Serializable接口;
  • Cat類實作readObjectNoData()方法;

目的:測試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.

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

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

推測:指定寫入的對象執行個體和讀取指定的對象執行個體為同一個。

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

InvalidClassException

s 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接口 代碼驗證

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;
        }
    }           
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]某一個時間點的值),把資料臨時或者永久地儲存到硬碟上(通常存放在檔案裡);
  • 利用序列化實作遠端通信,并且在資料傳遞過程中或者使用時能夠保證資料結構或者對象狀态的完整性和可傳遞性,即在網絡上傳送對象的位元組序列。

作者:忠勝

首發:「野指針」

來源:宜信技術學院