天天看點

java 序列化 關鍵字_Java 序列化Serializable 及 transient 關鍵字解讀

java 中的 Serializable 接口是用于實作對象 序列化和反序列化 的功能。那麼什麼是序列化和反序列化呢?

序列化: 就是将對象的目前狀态寫入位元組流,用于儲存或傳輸的過程

反序列化:則是從位元組流中恢複對象,是序列化的逆過程

通過序列化,能夠将目前 java 對象儲存至檔案,或通過網絡傳輸,并在需要時通過反序列化進行恢複。

序列化和反序列化

隻有實作了 Serializable 接口的類才能通過序列化功能進行儲存和恢複。下面進行示例說明:

下面定義了一個名為 UserInfo 的類,并實作 Serializable 接口

public class UserInfo implements Serializable {

private static final long serialVersionUID = -7701101573245777694L;

private String username;

private String password;

public UserInfo(String username, String password) {

this.username = username;

this.password = password;

}

@Override

public String toString() {

return "UserInfo [ username=" + username + ", password=" + password + "]";

}

}

通過 ObjectOutputStream 進行序列化輸出

public class SerializeDemo {

public static void main(String[] args) {

UserInfo userinfo = new UserInfo("eric", "eric_123456");

try (FileOutputStream fos = new FileOutputStream(new File("demo.txt"));

ObjectOutputStream oos = new ObjectOutputStream(fos)) {

oos.writeObject(userinfo);

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

}

我們看下demo.txt檔案夾内容

��sr*com.eric.learning.java._serialize.UserInfo� 0脎{肉��Lpasswordt�Ljava/lang/String;Lusernameq~�xpt�eric_123456t�eric

序列化後的檔案無法直接檢視,但是檔案中可以看到上文設定的成員變量值 eric 和 eric_123456

我們再通過 ObjectInputStream 進行反序列化恢複:

public class SerializeDemo {

public static void main(String[] args) {

UserInfo userinfo = null;

try (FileInputStream fis = new FileInputStream(new File("demo.txt"));

ObjectInputStream ois = new ObjectInputStream(fis)) {

userinfo = (UserInfo) ois.readObject();

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

System.out.println(userinfo);

}

}

輸出結果為:

UserInfo [username=eric, password=eric_123456]

serialVersionUID

在示例中,我們在實作 Serializable 接口時,生成了一個變量 serialVersionUID ,這個變量是幹嘛用的呢?

實際上這個變量是可序列化類的一個版本辨別。 在序列化對象時,對象的 serialVersionUID 會被一同寫入到位元組流,而反序列化時 JVM 會将位元組流中讀取的 serialVersionUID 同類的 serialVersionUID 比較,如果不相同則會反序列化失敗。

如果實作了 Serializable 接口,但不指定 serialVersionUID 的值,那麼編譯器在編譯時會自動生成一個 serialVersionUID.

示例:

假設我們增加了 Userinfo 中的字段:

public class UserInfo implements Serializable {

private static final long serialVersionUID = -7701101573245777694L;

private Long id;

private String username;

private String password;

}

更新後,我們希望舊 UserInfo 對象的序列化結果,無法再反序列化到新 UserInfo 類對象,這時我們可以重新生成 serialVersionUID 讓舊對象的序列化結果失效。

public class UserInfo implements Serializable {

// before

// private static final long serialVersionUID = -7701101573245777694L;

// now

private static final long serialVersionUID = -7701101573245777694L

private Long id;

private String username;

private String password;

}

這時如果反序列化舊 UserInfo 對象,将抛以下錯誤:

java.io.InvalidClassException: com.eric.learning.java._serialize.UserInfo; local class incompatible: stream classdesc serialVersionUID = -7701101573245777694, local class serialVersionUID = -7701101573245777695

at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)

at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1829)

at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)

transient

在 UserInfo 類中,有個用于存儲使用者密碼的 password 字段,密碼屬于私密資訊,我們不希望其被随意洩露。這時我們可以使用 transient 關鍵字。

用 transient 修飾的字段,在序列化時将被忽略:

public class UserInfo implements Serializable {

private static final long serialVersionUID = -7701101573245777694L;

private String username;

private transient String password; // transient 修飾

public UserInfo(String username, String password) {

this.username = username;

this.password = password;

}

}

再次序列化輸出到檔案:

public class SerializeDemo {

public static void main(String[] args) {

UserInfo userinfo = new UserInfo("eric", "eric_123456");

try (FileOutputStream fos = new FileOutputStream(new File("demo.txt"));

ObjectOutputStream oos = new ObjectOutputStream(fos)) {

oos.writeObject(userinfo);

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

}

檔案内容中已經沒有 password 内容:

��sr*com.eric.learning.java._serialize.UserInfo� 0脎{肉��Lusernamet�Ljava/lang/String;xpt�eric