在Java中如何使用transient
Java语言的transient不像class、synchronized和其他熟悉的关键字那样众所周知,因而它会出现在一些面试题中。这篇文章我将为大家讲解transient。
transient的用途
Q:transient关键字能实现什么?
A:当对象被序列化时(写入字节序列到目标文件)时,transient阻止实例中那些用此关键字声明的变量持久化;当对象被反序列化时(从源文件读取字节序列进行重构),这样的实例变量值不会被持久化和恢复。例如,当反序列化对象——数据流(例如,文件)可能不存在时,原因是你的对象中存在类型为java.io.InputStream的变量,序列化时这些变量引用的输入流无法被打开。
transient使用介绍
Q:如何使用transient?
A:包含实例变量声明中的transient修饰符。片段1提供了小的演示。
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
 | 
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。你或许会看到类似下面的输出:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
 | 
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
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
 | 
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)。你可以看到如下输出:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
 | 
w: 1x: 2y: 3z: 4w: 1x: 2y: 3z: 0 | 
这个输出告诉我们,实例变量y是被序列化的,z却没有,它被标记transient。但是,当Foo被序列化时,它并没有告诉我们,是否变量w和x被序列化和反序列化,是否只是以普通类初始化方式初始。对于答案,我们需要查看x.ser的内容。
下面显示x.ser十六进制:
| 
 1 
2 
 | 
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中的“The Java serialization algorithm revealed”这篇文章,我们发现输出的含义:
- 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不能被序列化,原因是它们类变量不能序列化。
                    
                
                
            
        
浙公网安备 33010602011771号