一、為什麼要序列化?
1、一般情況下,隻有當 JVM 處于運作時,Java 對象才可能存在,即這些對象的生命周期不會比 JVM 的生命周期更長。但在現實應用中,就可能要求在 JVM 停止運作之後能夠儲存(持久化)指定的對象,并在将來重新讀取被儲存的對象。Java 對象序列化就能夠幫助我們實作該功能。
2、在網絡或者程序通信中傳遞對象時,我們都需要使用序列化将 Java 對象轉換為位元組序列傳輸,具體表現為:發送資料前序列化對象,接收資料後反序列化對象。
二、序列化是什麼?
序列化指的是允許将實作序列化的 Java 對象轉換為位元組序列,這些位元組序列可以儲存在磁盤上,或通過網絡傳輸,以達到以後恢複成原來的對象。序列化機制使得對象可以脫離程式的運作而獨立存在。
通俗易懂的講,Java 序列化是指把 Java 對象轉換為位元組序列的過程,而 Java 反序列化是指把位元組序列恢複為 Java 對象的過程。
三、Java 序列化機制
1.使用 Serializable 接口實作序列化
在 Java 中, 隻要一個類實作了 java.io.Serializable 接口,那麼它就可以被序列化。
@Data
public class UserA implements Serializable {
/**
* 序列化ID
*/
private static final long serialVersionUID = 1L;
private int age;
private static String name = "張三";
@Override
public String toString() {
return "User{age=" + age + ",name=" + name + "}";
}
}
2.使用 Externalizable 接口實作序列化
Externalizable 繼承自 Serializable 接口,需要我們重寫 writeExternal() 與 readExternal() 方法來決定要序列化哪些資訊,并且必須要提供一個 public 的無參的構造器。
Externalizable 的性能要優于 Serializable,但也增加了複雜性。
@Data
public class UserB implements Externalizable {
/**
* 序列化ID
*/
private static final long serialVersionUID = 1L;
private int age;
private static String name = "李四";
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
age = in.readInt();
}
@Override
public String toString() {
return "User{age=" + age + ",name=" + name + "}";
}
}
3.序列化和反序列化
通過實作 Serializable 接口或者 Externalizable 接口,Java 對象已經具備序列化的資質了,那如何進行序列化和反序列化呢?這裡利用了 ObjectOutputStream 和 ObjectInputStream 對對象進行序列化及反序列化。
public static void main(String[] args) {
// 序列化
serialize();
// 反序列化
deserialize();
}
private static void serialize() {
UserA user = new UserA();
user.setAge(26);
//序列化對象到檔案中
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\userA"))) {
objectOutputStream.writeObject(user);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void deserialize() {
try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\userA"))) {
UserA user = (UserA) objectInputStream.readObject();
System.out.println(user);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
四、序列化分析
- serialVersionUID,即序列化ID。虛拟機能否進行反序列化,不僅取決于類路徑和功能代碼是否一緻,一個非常重要的一點是兩個類的序列化 ID 是否一緻,否則反序列化時會抛出 InvalidClassException 異常。serialVersionUID 預設是 1L,可以不顯示指定。
- 靜态變量不會被序列化。因為靜态變量存在于虛拟機方法區,屬于全局變量,是以在序列化或者反序列時,并不會儲存靜态變量。
- transient 關鍵字。作用是控制變量的序列化,在變量聲明前加上該關鍵字,可以阻止該變量被序列化到檔案中,在被反序列化後,transient 變量的值被設為初始值,如 int 型的是 0,對象型的是 null。
- 父子類的序列化。要想将父類對象也序列化,就需要讓父類也實作 Serializable(或 Externalizable) 接口。
- 引用類型成員變量的序列化。要想引用對象也序列化,就需要讓引用對象也實作 Serializable(或 Externalizable) 接口。