1. 程式人生 > >[Java] 序列化之基本認識

[Java] 序列化之基本認識

一、簡介

Java 序列化即將 Java 物件轉換為二進位制序列的過程,主要用於網路通訊、持久化儲存。

二、實現方式

Serializable

Java 中實現 Serializable 介面即可實現序列化。

Externalizable

Serializable 的子類,實現類需實現 writeExternal(), readExternal() 方法,和實現 writeObject(), readObject() 類似,可自行修改序列化或反序列化後的物件的資訊:
Externalizable

三、主要特性

serialVersionUID

兩個類的序列化ID不同,則無法相互序列化與反序列化。
要點:

  1. 可顯示指定 serialVersionUID
    : 指定固定值或隨機生成
  2. 未指定時,會預設生成一個 serialVersionUID: 根據類資訊自動生成

序列化ID不一致

SuperClass 類:

import java.io.Serializable;

/**
 * @author wengliemiao
 */
public class SuperClass implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name = "";

    private String age = "";
public String getName() { return name; } public void setName(String name) { this.name = name; } }

SerialVersionTest 測試類:

package com.wlm.jdk.serialVersion;

import java.io.*;

/**
 1. @author wengliemiao
 */
public class SerialVersionTest {

    public static void main
(String[] args) { SuperClass superClass = new SuperClass(); superClass.setName("2333"); try { ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("/Users/wengliemiao/Documents/timerange_test7")); oo.writeObject(superClass); oo.close(); } catch (IOException e) { e.printStackTrace(); } /* try { ObjectInputStream oi = new ObjectInputStream(new FileInputStream("/Users/wengliemiao/Documents/timerange_test7")); SuperClass superClass1 = (SuperClass) oi.readObject(); System.out.println(superClass1.getName()); oi.close(); } catch (Exception e) { e.printStackTrace(); } */ } }

操作步驟:

  1. SuperClass 類的 serialVersionUID 為1L時,執行 writeObject()
  2. SuperClass 類的 serialVersionUID 改為2L,執行 readObject();

結果為:
修改serialVersionUID

未指定序列化ID

類未執行序列化ID時,會自動生成一個,後續如果修改了類資訊,則會導致序列化ID不一致,從而導致反序列化失敗。
執行步驟:

  1. SuperClass 類資訊除不指定 serialVersionUID 外,其他資訊和前面一致,執行 writeObject()
  2. SuperClass 類隨意增加一個欄位,執行 readObject();

結果為:
未指定序列化ID

靜態變數序列化

靜態變數無法被序列化,因為序列化儲存的是物件的狀態,而靜態變數是類的狀態。
驗證步驟:

  1. SuperClass 類增加靜態變數b = 10;
  2. 執行序列化操作,並設定 b = 20;
  3. 執行反序列化操作,並輸出 b 的值;

測試 main 方法為:

public static void main(String[] args) {
        SuperClass superClass = new SuperClass();
        try {
            System.out.println("序列化前: " + superClass.b);
            ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("/Users/wengliemiao/Documents/timerange_test9"));
            oo.writeObject(superClass);
            oo.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        superClass.b = 20;

        try {
            ObjectInputStream oi = new ObjectInputStream(new FileInputStream("/Users/wengliemiao/Documents/timerange_test9"));
            SuperClass superClass1 = (SuperClass) oi.readObject();
            System.out.println("反序列化後: " + superClass1.b);
            oi.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

結果為:
靜態變數序列化

父類序列化

如果子類實現 Serializable 介面,而父類未實現,則序列化與反序列化時,父類資料不會參與。驗證如下:
SuperClass 類:

public class SuperClass {
    private String name = "";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

SubClass 類:

package com.wlm.jdk.serialVersion;

import java.io.Serializable;

/**
 * @author wengliemiao
 */
public class SubClass extends SuperClass implements Serializable {

    private static final long serialVersionUID = -1681989450035759750L;

    private String address = "";

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

測試類如下:

public static void main(String[] args) {
  SubClass subClass = new SubClass();
    subClass.setName("wlm");
    subClass.setAddress("火車東站");

    try {
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("/Users/wengliemiao/Documents/timerange_test11"));
        oo.writeObject(subClass);
        oo.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

    try {
        ObjectInputStream oi = new ObjectInputStream(new FileInputStream("/Users/wengliemiao/Documents/timerange_test11"));
        SubClass subClass1= (SubClass) oi.readObject();
        System.out.println("姓名: " + subClass1.getName());
        System.out.println("地址: " + subClass1.getAddress());
        oi.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

此時輸出資料為:
父類未實現Serializable
而為父類實現 Serializable 介面後,輸出為:
父類實現Serializable

transient 關鍵字

transient 關鍵字的作用則是阻止變數的序列化,被反序列化後,transient 變數的值被設為初始值。驗證如下, Subclass 類中增加 transient 變數:

private transient String sex;

其他程式碼與前面一致,輸出為:
transient

writeObject(), readObject()

可通過實現 writeObject(), readObject() 方法來進行自定義的序列化和反序列化。如果未實現,則預設呼叫是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。

如 JDK 中 ArrayList 類,elementData[] 欄位是 transient 型別,不會參與序列化:

/**
* The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData; // non-private to simplify nested class access

ArrayList 通過實現 writeObject(), readObject() 方法,序列化和反序列化 elementData[] 中的資料:

/**
 * Save the state of the <tt>ArrayList</tt> instance to a stream (that
 * is, serialize it).
 *
 * @serialData The length of the array backing the <tt>ArrayList</tt>
 *             instance is emitted (int), followed by all of its elements
 *             (each an <tt>Object</tt>) in the proper order.
 */
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

/**
 * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
 * deserialize it).
 */
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;

    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in capacity
    s.readInt(); // ignored

    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}