首頁 >Java >Java入門 >Java之物件的序列化與反序列化

Java之物件的序列化與反序列化

青灯夜游
青灯夜游轉載
2019-11-27 17:59:222548瀏覽

Java之物件的序列化與反序列化

物件的序列化與反序列化

#1)物件序列化,就是將Object物件轉換成byte序列,反之叫對象的反序列化。

2)序列化流(ObjectOutputStream),是位元組的篩選流- writeObject()方法

     反序列化流(ObjectInputStream)- readObject()方法

#3)序列化介面(Serializable)

物件必須實現序列化接口,才能進行序列化,否則將出現異常。

註:這個接口,沒有任何方法,只是一個【標準】

一、最基本的序列化和反序列過程

序列化和反序列都是以Object物件進行操作的,這裡透過一個簡單的案例來給大家示範一下物件序列化和反序列化的過程。

1、新建一個Student類別(測試類別)

#注意:需要實作序列化介面的類別才能進行序列化操作! !

@SuppressWarnings("serial")
public class Student implements Serializable{
    private String stuno;//id
    private String stuna;//姓名
    private int stuage;//年龄
    public String getStuno() {
        return stuno;
    }
    public void setStuno(String stuno) {
        this.stuno = stuno;
    }
    public String getStuna() {
        return stuna;
    }
    public void setStuna(String stuna) {
        this.stuna = stuna;
    }
    public Student() {
        super();
        // TODO Auto-generated constructor stub
    }
    public Student(String stuno, String stuna, int stuage) {
        super();
        this.stuno = stuno;
        this.stuna = stuna;
        this.stuage = stuage;
    }
    @Override
    public String toString() {
        return "Student [stuno=" + stuno + ", stuna=" + stuna + ", stuage=" + stuage + "]";
    }
    public int getStuage() {
        return stuage;
    }
    public void setStuage(int stuage) {
        this.stuage = stuage;
    }
}

2、將Student類別的實例序列化成檔案

#基本操作步驟如下:

#1)、指定序列化保存的檔案

2)、建構ObjectOutputStream類別

3)、建構一個Student類別

4)、使用writeObject方法序列化

5)、使用close()方法關閉流程

String file="demo/obj.dat";
        //对象的序列化
        ObjectOutputStream oos=new ObjectOutputStream(
                new FileOutputStream(file));
        //把Student对象保存起来,就是对象的序列化
        Student stu=new Student("01","mike",18);
        //使用writeObject方法序列化
        oos.writeObject(stu);
        oos.close();

執行結果:可以看到demo目錄下產生了obj.dat的序列化檔案

## 3.將檔案反序列化讀出Student類別物件

#基本操作步驟如下:

1)、指定反序列化的檔案

2)、建構ObjectInputStream類別

3)、使用readObject方法反序列化

1)、使用close方法關閉流

String file="demo/obj.dat";
        ObjectInputStream ois =new ObjectInputStream(
                new FileInputStream(file));
        //使用readObject()方法序列化
        Student stu=(Student)ois.readObject();//强制类型转换
        System.out.println(stu);
        ois.close();
執行結果:

#注意:在檔案反序列化時,readObject方法取出的物件預設都是Object類型,必須強制轉換為對應的類型。

二、transient及ArrayList原始碼分析

#在日常程式設計過程中,我們有時不希望一個類別所有的元素都被編譯器序列化,這時該怎麼辦?

Java提供了一個

transient關鍵字來修飾我們不希望被jvm自動序列化的元素。下面簡單來講解一下這個關鍵字。

transient 關鍵字:被transient修飾的元素,該元素不會進行jvm預設的序列化,但可以自行完成這個元素的序列化。

注意:

1)在以後的網路程式設計中,如果有某些元素不需要傳輸,那就可以用transient修飾,來節省流量;對有效元素序列化,提高效能。

2)可以使用writeObject自己完成這個元素的序列化。

ArrayList就是用了此方法進行了最佳化操作。 ArrayList最核心的容器Object[] elementData使用了transient修飾,但在writeObject自己實作對elementData陣列的序列化。只對數組中有效元素進行序列化。 readObject與此類似。

--------------自己序列化的方式- --------------

在要序列化的類別中加入兩個方法(

這兩個方法都是從ArrayList原始碼中提取出來的,比較特殊的兩個方法):

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
        s.defaultWriteObject();//把jvm能默认序列化的元素进行序列化操作
        s.writeInt(stuage);//自己完成被transient修饰的元素的序列化
    }
    private void readObject(java.io.ObjectInputStream s) throws java.io.IOException,ClassNotFoundException{
        s.defaultReadObject();//把jvm能默认反序列化的元素进行反序列化操作
        this.stuage=s.readInt();//自己完成stuage的反序列化操作
    }
加入這兩個方法後,即使被transient修飾的元素也能像剛剛那樣進行序列化和反序列化了, jvm會自動使用這兩個方法來幫助我們完成這動作。

這裡又有個問題,為什麼還需要手動去完成序列化和反序列化呢,有什麼意義呢?

這個問題得再從ArrayList的源碼去分析:

可以看出ArrayList原始碼中自己序列化的目的:ArrayList底層為數組,自己序列化可以過濾數組中無效的元素,只序列化數組中有效的元素,

從而提高性能

因此,實際編程過程中我們可以根據需要來自己完成序列化以提高效能。

三、序列化中子父类构造函数问题

在类的序列化和反序列化中,如果存在子类和父类的关系时,序列化和反序列化的过程又是怎么样的呢?

这里我写一个测试类来测试子类和父类实现序列化和反序列化时构造函数的实现变化。

public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
        String file="demo/foo.dat";
        ObjectOutputStream oos=new ObjectOutputStream(
                new FileOutputStream(file));
        Foo2 foo2 =new Foo2();
        oos.writeObject(foo2);
        oos.flush();
        oos.close();
    }

}
class Foo implements Serializable{
    public Foo(){
        System.out.println("foo");
    }
}
class Foo1 extends Foo{
    public Foo1(){
        System.out.println("foo1");
    }
    
}
class Foo2 extends Foo1{
    public Foo2(){
        System.out.println("foo2");
    }
}

运行结果:这是序列化时递归调用了父类的构造函数

接来下看看反序列化时,是否递归调用父类的构造函数。

ObjectInputStream ois=new ObjectInputStream(
new FileInputStream(file));
Foo2 foo2=(Foo2)ois.readObject();
ois.close();

运行结果:控制台没有任何输出。

那么这个结果是否证明反序列化过程中父类的构造函数就是始终不调用的呢?

然而不能证明!!

因为再看下面这个不同的测试例子:

class Bar {
    public Bar(){
        System.out.println("bar");
    }
}
class Bar1 extends Bar implements Serializable{
    public Bar1(){
        System.out.println("bar1");
    }
}
class Bar2 extends Bar1{
    public Bar2(){
        System.out.println("bar2");
    }
}

我们用这个例子来测试序列化和反序列化。

序列化结果:

反序列化结果:没实现序列化接口的父类被显示调用构造函数

【反序列化时】,向上递归调用构造函数会从【可序列化的一级父类结束】。即谁实现了可序列化(包括继承实现的),谁的构造函数就不会调用。

总结:

1)父类实现了serializable接口,子类继承就可序列化。

子类在反序列化时,父类实现了序列化接口,则不会递归调用其构造函数。

2)父类未实现serializable接口,子类自行实现可序列化

子类在反序列化时,父类没有实现序列化接口,则会递归调用其构造函数。

本文来自 java入门 栏目,欢迎学习!

以上是Java之物件的序列化與反序列化的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:cnblogs.com。如有侵權,請聯絡admin@php.cn刪除