Java學習81:序列化

在這裏插入圖片描述
序列化是指把一個Java對象變成二進制內容,本質上就是一個byte[]數組。

爲什麼要把Java對象序列化呢?因爲序列化之後可以把byte[]保持到文件中,或者把byte[]通過網絡傳輸到遠程,這樣,就相當於把Java對象存儲到文件或者通過網絡傳輸出去了。

有序列化,就有反序列化,即把一個二進制內容(也就是byte[]數組)變回Java對象。有了反序列化,保存到文件中的byte[]數組又可以“變回”Java對象,或者從網絡上讀取byte[]並把它“變回”Java對象。

我們來看看如何把一個Java對象序列化。

一個Java對象要能序列化,必須實現一個特殊的java.io.Serializable接口,它的定義如下:

public interface Serializable {
}

Serializable接口沒有定義任何方法,它是一個空接口。我們把這樣的空接口稱爲“標記接口”(Marker Interface),實現了標記接口的類僅僅是給自身貼了個“標記”,並沒有增加任何方法。

序列化
把一個Java對象變爲byte[]數組,需要使用ObjectOutputStream。它負責把一個Java對象寫入一個字節流:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Arrays;

public class Demo05 {
    public static void main(String[] args) throws IOException {
        ByteArrayOutputStream buffer=new ByteArrayOutputStream();
        try(ObjectOutputStream output=new ObjectOutputStream(buffer)){
            // 寫入int
            output.writeInt(12345);
            // 寫入String
            output.writeUTF("Hello");
            // 寫入Object
            output.writeObject(Double.valueOf(123.456));
        }
        System.out.println(Arrays.toString(buffer.toByteArray()));
    }
}

ObjectOutputStream既可以寫入基本類型,如int,boolean,也可以寫入String(以UTF-8編碼),還可以寫入實現了Serializable接口的Object。

因爲寫入Object時需要大量的類型信息,所以寫入的內容很大。

反序列化
和ObjectOutputStream相反,ObjectInputStream負責從一個字節流讀取Java對象:

try (ObjectInputStream input = new ObjectInputStream(...)) {
    int n = input.readInt();
    String s = input.readUTF();
    Double d = (Double) input.readObject();
}

除了能讀取基本類型和String類型外,調用readObject()可以直接返回一個Object對象。要把它變成一個特定類型,必須強制轉型。

readObject()可能拋出的異常有:

  • ClassNotFoundException:沒有找到對應的Class;
  • InvalidClassException:Class不匹配。

對於ClassNotFoundException,這種情況常見於一臺電腦上的Java程序把一個Java對象,例如,Person對象序列化以後,通過網絡傳給另一臺電腦上的另一個Java程序,但是這臺電腦的Java程序並沒有定義Person類,所以無法反序列化。

對於InvalidClassException,這種情況常見於序列化的Person對象定義了一個int類型的age字段,但是反序列化時,Person類定義的age字段被改成了long類型,所以導致class不兼容。

爲了避免這種class定義變動導致的不兼容,Java的序列化允許class定義一個特殊的serialVersionUID靜態變量,用於標識Java類的序列化“版本”,通常可以由IDE自動生成。如果增加或修改了字段,可以改變serialVersionUID的值,這樣就能自動阻止不匹配的class版本:

public class Person implements Serializable {
    private static final long serialVersionUID = 2709425275741743919L;
}

要特別注意反序列化的幾個重要特點:

反序列化時,由JVM直接構造出Java對象,不調用構造方法,構造方法內部的代碼,在反序列化時根本不可能執行。

安全性
因爲Java的序列化機制可以導致一個實例能直接從byte[]數組創建,而不經過構造方法,因此,它存在一定的安全隱患。一個精心構造的byte[]數組被反序列化後可以執行特定的Java代碼,從而導致嚴重的安全漏洞。

實際上,Java本身提供的基於對象的序列化和反序列化機制既存在安全性問題,也存在兼容性問題。更好的序列化方法是通過JSON這樣的通用數據結構來實現,只輸出基本類型(包括String)的內容,而不存儲任何與代碼相關的信息。

小結
可序列化的Java對象必須實現java.io.Serializable接口,類似Serializable這樣的空接口被稱爲“標記接口”(Marker Interface);

反序列化時不調用構造方法,可設置serialVersionUID作爲版本號(非必需);

Java的序列化機制僅適用於Java,如果需要與其它語言交換數據,必須使用通用的序列化方法,例如JSON。

謝謝觀看

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