在 Java 中如何使用 transient

longmanma發表於2021-09-09


Java語言的transient不像class、synchronized和其他熟悉的關鍵字那樣眾所周知,因而它會出現在一些面試題中。這篇文章我將為大家講解transient。

transient的用途

Q:transient關鍵字能實現什麼?

A:當物件被序列化時(寫入位元組序列到目標檔案)時,transient阻止例項中那些用此關鍵字宣告的變數持久化;當物件被反序列化時(從原始檔讀取位元組序列進行重構),這樣的例項變數值不會被持久化和恢復。例如,當反序列化物件——資料流(例如,檔案)可能不存在時,原因是你的物件中存在型別為java.io.InputStream的變數,序列化時這些變數引用的輸入流無法被開啟。

transient使用介紹

Q:如何使用transient?

A:包含例項變數宣告中的transient修飾符。片段1提供了小的演示。

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;class ClassLib implements Serializable {    private transient InputStream is;    private int majorVer;    private int minorVer;

    ClassLib(InputStream is) throws IOException {
        System.out.println("ClassLib(InputStream) called");        this.is = is;
        DataInputStream dis;        if (is instanceof DataInputStream)
            dis = (DataInputStream) is;        else
            dis = new DataInputStream(is);        if (dis.readInt() != 0xcafebabe)            throw new IOException("not a .class file");
        minorVer = dis.readShort();
        majorVer = dis.readShort();
    }    int getMajorVer() {        return majorVer;
    }    int getMinorVer() {        return minorVer;
    }    void showIS() {
        System.out.println(is);
    }
}public class TransDemo {    public static void main(String[] args) throws IOException {        if (args.length != 1) {
            System.err.println("usage: java TransDemo classfile");            return;
        }
        ClassLib cl = new ClassLib(new FileInputStream(args[0]));
        System.out.printf("Minor version number: %d%n", cl.getMinorVer());
        System.out.printf("Major version number: %d%n", cl.getMajorVer());
        cl.showIS();        try (FileOutputStream fos = new FileOutputStream("x.ser");
                ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(cl);
        }

        cl = null;        try (FileInputStream fis = new FileInputStream("x.ser");
                ObjectInputStream ois = new ObjectInputStream(fis)) {
            System.out.println();
            cl = (ClassLib) ois.readObject();
            System.out.printf("Minor version number: %d%n", cl.getMinorVer());
            System.out.printf("Major version number: %d%n", cl.getMajorVer());
            cl.showIS();
        } catch (ClassNotFoundException cnfe) {
            System.err.println(cnfe.getMessage());
        }
    }
}

片段1:序列化和反序列化ClassLib物件

片段1中宣告ClassLib和TransDemo類。ClassLib是一個讀取Java類檔案的庫,並且實現了java.io.Serializable介面,從而這些例項能被序列化和反序列化。TransDemo是一個用來序列化和反序列化ClassLib例項的應用類。

ClassLib宣告它的例項變數為transient,原因是它可以毫無意義的序列化一個輸入流(像上面講述的那樣)。事實上,如果此變數不是transient的話,當反序列化x.ser的內容時,則會丟擲java.io.NotSerializableException,原因是InputStream沒有實現Serializable介面。

編譯片段1:javac TransDemo.java;帶一個引數TransDemo.class執行應用:java TransDemo TransDemo.class。你或許會看到類似下面的輸出:

ClassLib(InputStream) calledMinor version number: 0Major version number: 51java.io.FileInputStream@79f1e0e0Minor version number: 0Major version number: 51null

以上輸出表明:當物件被重構時,沒有構造方法呼叫。此外,is假定預設為null,相比較,當ClassLib物件序列化時,majorVer和minorVer是有值的。

類中的成員變數和transient

Q:類中的成員變數中可以使用transient嗎?

A:問題答案請看片段2

public class TransDemo {    public static void main(String[] args) throws IOException {
        Foo foo = new Foo();
        System.out.printf("w: %d%n", Foo.w);
        System.out.printf("x: %d%n", Foo.x);
        System.out.printf("y: %d%n", foo.y);
        System.out.printf("z: %d%n", foo.z);        try (FileOutputStream fos = new FileOutputStream("x.ser");
                ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(foo);
        }

        foo = null;        try (FileInputStream fis = new FileInputStream("x.ser");
                ObjectInputStream ois = new ObjectInputStream(fis)) {
            System.out.println();
            foo = (Foo) ois.readObject();
            System.out.printf("w: %d%n", Foo.w);
            System.out.printf("x: %d%n", Foo.x);
            System.out.printf("y: %d%n", foo.y);
            System.out.printf("z: %d%n", foo.z);
        } catch (ClassNotFoundException cnfe) {
            System.err.println(cnfe.getMessage());
        }
    }
}

片段2:序列化和反序列化Foo物件

片段2有點類似片段1。但不同的是,序列化和反序列化的是Foo物件,而不是ClassLib。此外,Foo包含一對變數,w和x,以及例項變數y和z。

編譯片段2(javac TransDemo.java)並執行應用(java TransDemo)。你可以看到如下輸出:

w: 1x: 2y: 3z: 4w: 1x: 2y: 3z: 0

這個輸出告訴我們,例項變數y是被序列化的,z卻沒有,它被標記transient。但是,當Foo被序列化時,它並沒有告訴我們,是否變數w和x被序列化和反序列化,是否只是以普通類初始化方式初始。對於答案,我們需要檢視x.ser的內容。

下面顯示x.ser十六進位制:

00000000 AC ED 00 05 73 72 00 03 46 6F 6F FC 7A 5D 82 1D ....sr..Foo.z]..
00000010 D2 9D 3F 02 00 01 49 00 01 79 78 70 00 00 00 03 ..?...I..yxp....

由於JavaWorld中的“”這篇文章,我們發現輸出的含義:

  • AC ED 序列化協議標識

  • 00 05 流版本號

  • 73 表示這是一個新物件

  • 72 表示這是一個新的類

  • 00 03 表示類名長度(3)

  • 46 6F 6F 表示類名(Foo)

  • FC 7A 5D 82 1D D2 9D 3F 表示類的序列版本識別符號

  • 02 表示該物件支援序列化

  • 00 01 表示這個類的變數數量(1)

  • 49 變數型別程式碼 (0×49, 或I, 表示int)

  • 00 01 表示變數名長度(1)

  • 79 變數名稱(y)

  • 78 表示該物件可選的資料塊末端

  • 70 表示我們已經到達類層次結構的頂部

  • 00 00 00 03 表示y的值(3)

顯而易見,只有例項變數y被序列化。因為z是transient,所以不能序列化。此外,即使它們標記transien,w和x不能被序列化,原因是它們類變數不能序列化。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2730/viewspace-2809802/,如需轉載,請註明出處,否則將追究法律責任。

相關文章