Java 中的 transient 关键字:深入解析与实战
在 Java 编程中,transient
关键字是一个非常有用的工具,尤其是在处理对象序列化时。尽管 transient
关键字在日常开发中可能不常被使用,但了解它的作用和使用场景对于提升代码的安全性和性能至关重要。本文将深入探讨 transient
关键字的作用、使用方法以及一些注意事项。
1 transient
的作用及使用方法
在 Java 中,一个对象只要实现了 Serializable
接口,它就可以被序列化。然而,在实际开发过程中,我们常常会遇到这样的情况:一个类的某些字段需要序列化,而另一些字段则不需要。例如,用户的敏感信息(如密码、银行卡号等)为了安全起见,不希望在网络操作中传输或持久化到磁盘文件中。这时,我们就可以使用 transient
关键字来标记这些字段。
示例
public class TransientTest {
public static void main(String[] args) {
User user = new User();
user.setUsername("沉默王二");
user.setPasswd("123456");
System.out.println("read before Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("user.txt"));
os.writeObject(user); // 将User对象写进文件
os.flush();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("user.txt"));
user = (User) is.readObject(); // 从流中读取User的数据
is.close();
System.out.println("\nread after Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User implements Serializable {
private static final long serialVersionUID = 8294180014912103005L;
private String username;
private transient String passwd;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
输出结果:
read before Serializable:
username: 沉默王二
password: 123456
read after Serializable:
username: 沉默王二
password: null
从输出结果可以看出,password
字段为 null
,说明反序列化时根本没有从文件中获取到该字段的信息。
2 transient 使用小结
- 一旦字段被
transient
修饰,该成员变量将不再是对象持久化的一部分,该变量的值在序列化后无法访问。 transient
关键字只能修饰字段,而不能修饰方法和类。- 被
transient
关键字修饰的字段不能被序列化,一个静态变量(static
关键字修饰)不管是否被transient
修饰,均不能被序列化。
示例
public class TransientTest {
public static void main(String[] args) {
User user = new User();
user.setUsername("沉默王二");
user.setPasswd("123456");
System.out.println("read before Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("user.txt"));
os.writeObject(user); // 将User对象写进文件
os.flush();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
// 在反序列化之前改变username的值
User.username = "沉默王三";
ObjectInputStream is = new ObjectInputStream(new FileInputStream("user.txt"));
user = (User) is.readObject(); // 从流中读取User的数据
is.close();
System.out.println("\nread after Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User implements Serializable {
private static final long serialVersionUID = 8294180014912103005L;
public static String username;
private transient String passwd;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
运行结果:
read before Serializable:
username: 沉默王二
password: 123456
read after Serializable:
username: 沉默王三
password: null
从结果可以看出,username
字段在反序列化后变成了 沉默王三
,这证明了 static
修饰的字段不能被序列化。
3 transient
修饰的字段真的不能被序列化?
思考下面的例子:
public class ExternalizableTest implements Externalizable {
private transient String content = "是的,我将会被序列化,不管我是否被transient关键字修饰";
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(content);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
content = (String) in.readObject();
}
public static void main(String[] args) throws Exception {
ExternalizableTest et = new ExternalizableTest();
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(new File("test")));
out.writeObject(et);
ObjectInput in = new ObjectInputStream(new FileInputStream(new File("test")));
et = (ExternalizableTest) in.readObject();
System.out.println(et.content);
out.close();
in.close();
}
}
输出结果:
是的,我将会被序列化,不管我是否被transient关键字修饰
这是为什么呢?不是说 transient
关键字修饰的字段不能序列化吗?
这是因为我们使用了 Externalizable
接口而不是 Serializable
接口。在 Java 中,对象的序列化可以通过实现两种接口来实现:
Serializable
接口:所有的序列化将会自动进行。Externalizable
接口:需要在writeExternal
方法中指定要序列化的字段,与transient
关键字修饰无关。
因此,例子中输出的是变量 content
的内容,而不是 null
。
4 小结
transient
关键字用于修饰类的成员变量,在序列化对象时,被修饰的成员变量不会被序列化和保存到文件中。其作用是告诉 JVM 在序列化对象时不需要将该变量的值持久化,这样可以避免一些安全或者性能问题。但是,transient
修饰的成员变量在反序列化时会被初始化为其默认值(如 int
类型会被初始化为 0
,引用类型会被初始化为 null
),因此需要在程序中进行适当的处理。
transient
关键字和 static
关键字都可以用来修饰类的成员变量。其中,transient
关键字表示该成员变量不参与序列化和反序列化,而 static
关键字表示该成员变量是属于类的,不属于对象的,因此不需要序列化和反序列化。
在 Serializable
和 Externalizable
接口中,transient
关键字的表现也不同:
- 在
Serializable
中,transient
表示该成员变量不参与序列化和反序列化。 - 在
Externalizable
中,transient
不起作用,因为Externalizable
接口需要实现readExternal
和writeExternal
方法,需要手动完成序列化和反序列化的过程。
通过深入理解 transient
关键字,我们可以更好地控制对象的序列化过程,从而提升代码的安全性和性能。
5 思维导图
6 参考链接
招银面试官:说说 Java transient 关键字吧