transient关键字

transient关键字是开发中用的比较少的一个关键字,它在序列化和反序列化中比较重要,通常面试时会考察它的作用和它的使用场景,还有它在什么情况下会失效。

transient 的作用

作为Java基础知识的一个点,transient的作用大家都知道是用来防止属性被序列化。因此它出现的场景都会同时有 Serializable 接口。

它的使用场景比较容易理解,比方当我们在序列化并以本地文件或其他持久化数据形式存储用户资料时,像用户的密码这样的字段我们是不希望存储的,这样的字段就需要用 transient 来修饰了。

比如下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UserBean implements Serializable {
private String name;
private transient String password;

public String getName() {
return name;
}

public String getPassword() {
return password;
}

public void setName(String name) {
this.name = name;
}

public void setPassword(String password) {
this.password = password;
}

public String toString() {
return "name: " + this.name + " psw: " + this.password;
}
}

我们定义了个UserBean类,然后再用另外一个类来持久化用户数据,观察在序列化和反序列化的过程中数据发生了什么变化。下面是主代码

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
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class TransientDemo {
public static void main(String[] args) {
UserBean user = new UserBean();
user.setName("Jackson");
user.setPassword("password123");
System.out.println("User: " + user.toString());
// begin serializing
try {
ObjectOutputStream fos = new ObjectOutputStream(
new FileOutputStream("bean.txt"));
fos.writeObject(user);
fos.flush();
fos.close();
System.out.println("local serialized done");
} catch (Exception e) {

}
System.out.println("de-serialzing...");
try {
ObjectInputStream fis = new ObjectInputStream(new FileInputStream(
"bean.txt"));
user = (UserBean) fis.readObject();
fis.close();
System.out.println("User de-serialzed: " + user.toString());
} catch (Exception e) {

}
}
}

输出

1
2
3
4
5
User: name: Jackson psw: password123
local serialized done
de-serialzing…
User de-serialzed: name: Jacksonpsw: null
对比 name和password字段,被 transient修饰的密码字段在序列化后就没有被持久化了。

transient的局限

transient能作用的场景只能是和 Serializable接口搭配使用,而另外一个序列化接口, Externalizable却不能对它起效。

仔细思考就明白原因,Serializable是JVM自动进行序列化的,而 Externalizable需要我们通过 readExternalwriteExternal 两个方法自己定义序列化和反序列化的实现方式,因此即使被 transient修饰也和能否被序列化无关。

局限二,

static修饰的变量也不能被序列化,与是否被 transient修饰无关。

如何理解这句话呢。可以把demo中的 name改成 static来尝试下,

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
public class UserBean implements Serializable { 
private static String name;
private transient String password;
}
public class TransientDemo {
public static void main(String[] args) {
UserBean user = new UserBean();
user.setName("Jackson");
user.setPassword("password123");
System.out.println("User: " + user.toString());
//begin serializing
try {
ObjectOutputStream fos = new ObjectOutputStream(new FileOutputStream("bean.txt"));
fos.writeObject(user);
fos.flush();
fos.close();
System.out.println("local serialized done");
} catch (Exception e) {

}
System.out.println("de-serialzing...");
try {
UserBean.name = "John Doe";
ObjectInputStream fis = new ObjectInputStream(new FileInputStream("bean.txt"));
user = (UserBean) fis.readObject();
fis.close();
System.out.println("User de-serialzed: " + user.toString());
} catch(Exception e) {

}
}
}

输出

1
2
3
4
User: name: Jackson psw: password123
local serialized done
de-serialzing…
User de-serialzed: name:John Doepsw: null

可以看出,即使被反序列化,static变量并没有拿到序列化时的值,因为 static变量的值是保存在JVM堆中,并不会受到序列化的影响。这个是容易被忽略的坑,需要留意一下。