[java]transient關鍵字

加瓦一枚發表於2019-03-11

先解釋下什麼是序列化

我們的物件並不只是存在記憶體中,還需要傳輸網路,或者儲存起來下次再載入出來用,所以需要Java序列化技術。

Java序列化技術正是將物件轉變成一串由二進位制位元組組成的陣列,可以通過將二進位制資料儲存到磁碟或者傳輸網路,磁碟或者網路接收者可以在物件的屬類的模板上來反序列化類的物件,達到物件持久化的目的。

更多序列化請參考:《關於Java序列化你應該知道的一切》這篇文章。

什麼是 transient?

簡單來說就是,被 transient 修飾的變數不能被序列化。

具體來看下面的示例1

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * @author 微信公眾號:Java技術棧
 */
public class TransientTest {

    public static void main(String[] args) throws Exception {

        User user = new User();
        user.setUsername("Java技術棧");
        user.setId("javastack");

        System.out.println("\n序列化之前");
        System.out.println("username: " + user.getUsername());
        System.out.println("id: " + user.getId());

        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("d:/user.txt"));
        os.writeObject(user);
        os.flush();
        os.close();

        ObjectInputStream is = new ObjectInputStream(new FileInputStream("d:/user.txt"));
        user = (User) is.readObject();
        is.close();

        System.out.println("\n序列化之後");
        System.out.println("username: " + user.getUsername());
        System.out.println("id: " + user.getId());

    }
}

/**
 * @author 微信公眾號:Java技術棧
 */
class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String username;
    private transient String id;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

}

輸出結果:

序列化之前
username: Java技術棧
id: javastack

序列化之後
username: Java技術棧
id: null

示例1在 id 欄位上加了 transient 關鍵字修飾,反序列化出來之後值為 null,說明了被 transient 修飾的變數不能被序列化。

靜態變數能被序列化嗎?

這個話題也是最近棧長的Java技術棧vip群裡面討論的,大家對這個知識點比較模糊,我就寫了這篇文章測試總結一下。

如果你也想加入我們的Java技術棧vip群和各位大牛一起討論技術,那點選這個連結瞭解加入吧。

那麼,到底靜態變數能被序列化嗎?廢話少說,先動手測試下吧!

示例2:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * @author 微信公眾號:Java技術棧
 */
public class TransientStaticTest {

    public static void main(String[] args) throws Exception {

        User2 user = new User2();
        User2.username = "Java技術棧1";
        user.setId("javastack");

        System.out.println("\n序列化之前");
        System.out.println("username: " + user.getUsername());
        System.out.println("id: " + user.getId());

        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("d:/user.txt"));
        os.writeObject(user);
        os.flush();
        os.close();
        
        // 在反序列化出來之前,改變靜態變數的值
        User2.username = "Java技術棧2";

        ObjectInputStream is = new ObjectInputStream(new FileInputStream("d:/user.txt"));
        user = (User2) is.readObject();
        is.close();

        System.out.println("\n序列化之後");
        System.out.println("username: " + user.getUsername());
        System.out.println("id: " + user.getId());

    }
}

/**
 * @author 微信公眾號:Java技術棧
 */
class User2 implements Serializable {

    private static final long serialVersionUID = 1L;

    public static String username;
    private transient String id;

    public String getUsername() {
        return username;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

}

輸出結果:

序列化之前
username: Java技術棧1
id: javastack

序列化之後
username: Java技術棧2
id: null

示例2把 username 改為了 public static, 並在反序列化出來之前改變了靜態變數的值,結果可以看出序列化之後的值並非序列化進去時的值。

由以上結果分析可知,靜態變數不能被序列化,示例2讀取出來的是 username 在 JVM 記憶體中儲存的值。

transient 真不能被序列化嗎?

繼續來看示例3:

import java.io.Externalizable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

/**
 * @author 微信公眾號:Java技術棧
 */
public class ExternalizableTest {

    public static void main(String[] args) throws Exception {

        User3 user = new User3();
        user.setUsername("Java技術棧");
        user.setId("javastack");
        ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream(new File("javastack")));
        objectOutput.writeObject(user);

        ObjectInput objectInput = new ObjectInputStream(new FileInputStream(new File("javastack")));
        user = (User3) objectInput.readObject();

        System.out.println(user.getUsername());
        System.out.println(user.getId());

        objectOutput.close();
        objectInput.close();
    }

}

/**
 * @author 微信公眾號:Java技術棧
 */
class User3 implements Externalizable {

    private static final long serialVersionUID = 1L;

    public User3() {

    }

    private String username;
    private transient String id;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public void writeExternal(ObjectOutput objectOutput) throws IOException {
        objectOutput.writeObject(id);
    }

    @Override
    public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {
        id = (String) objectInput.readObject();
    }

}

輸出結果:

null
javastack

示例3的 id 被 transient 修改了,為什麼還能序列化出來?那是因為 User3 實現了介面 Externalizable,而不是 Serializable。

在 Java 中有兩種實現序列化的方式,Serializable 和 Externalizable,可能大部分人只知道 Serializable 而不知道 Externalizable。

這兩種序列化方式的區別是:實現了 Serializable 介面是自動序列化的,實現 Externalizable 則需要手動序列化,通過 writeExternal 和 readExternal 方法手動進行,這也是為什麼上面的 username 為 null 的原因了。

transient 關鍵字總結

1)transient修飾的變數不能被序列化;

2)transient只作用於實現 Serializable 介面;

3)transient只能用來修飾普通成員變數欄位;

4)不管有沒有 transient 修飾,靜態變數都不能被序列化;

相關文章