Java序列化——transient关键字和Externalizable接口

简介:

    提到Java序列化,相信大家都不陌生。我们在序列化的时候,需要将被序列化的类实现Serializable接口,这样的类在序列化时,会默认将所有的字段都序列化。那么当我们在序列化Java对象时,如果不希望对象中某些字段被序列化(如密码字段),怎么实现呢?看一个例子:

?
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
import  java.io.Serializable;
import  java.util.Date;
 
public  class  LoginInfo  implements  Serializable {
     private  static  final  long  serialVersionUID = 8364988832581114038L;
     private  String userName;
     private  transient  String password; //Note this key word "transient"
     private  Date loginDate;
     
     //Default Public Constructor
     public  LoginInfo() {
         System.out.println( "LoginInfo Constructor" );
     }
     
     //Non-Default constructor
     public  LoginInfo(String username, String password) {
         this .userName = username;
         this .password = password;
         loginDate =  new  Date();
     }
     public  String toString() {
         return  "UserName="  + userName +  ", Password=" 
                 + password +  ", LoginDate="  + loginDate;
     }
}

    测试类:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import  java.io.FileInputStream;
import  java.io.FileOutputStream;
import  java.io.ObjectInputStream;
import  java.io.ObjectOutputStream;
 
public  class  Test {
     static  String fileName =  "C:/x.file" ;
     public  static  void  main(String[] args)  throws  Exception {
         LoginInfo info =  new  LoginInfo( "name" "123" );
         System.out.println(info);
         //Write
         System.out.println( "Serialize object" );
         ObjectOutputStream oos =  new  ObjectOutputStream( new  FileOutputStream(fileName));
         oos.writeObject(info);
         oos.close();
         //Read
         System.out.println( "Deserialize object." );
         ObjectInputStream ois =  new  ObjectInputStream( new  FileInputStream(fileName));
         LoginInfo info2 = (LoginInfo)ois.readObject();
         ois.close();
         System.out.println(info2);
     }
}

    执行结果:

?
1
2
3
4
UserName=name, Password= 123 , LoginDate=Wed Nov  04  16 : 41 : 49  CST  2015
Serialize object
Deserialize object.
UserName=name, Password= null , LoginDate=Wed Nov  04  16 : 41 : 49  CST  2015

    另一种可以达到此目的的方法可能就比较少用了,那就是——不实现Serializable而实现Externalizable接口。这个Externalizable接口有两个方法,分别表示在序列化的时候需要序列化哪些字段和反序列化的时候能够反序列化哪些字段

?
1
2
void  writeExternal(ObjectOutput out)  throws  IOException;
void  readExternal(ObjectInput in)  throws  IOException, ClassNotFoundException;

    于是就有了下面的代码:

?
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
import  java.io.Externalizable;
import  java.io.IOException;
import  java.io.ObjectInput;
import  java.io.ObjectOutput;
import  java.util.Date;
 
public  class  LoginInfo2  implements  Externalizable {
     private  static  final  long  serialVersionUID = 8364988832581114038L;
     private  String userName;
     private  String password;
     private  Date loginDate;
     
     //Default Public Constructor
     public  LoginInfo2() {
         System.out.println( "LoginInfo Constructor" );
     }
     
     //Non-Default constructor
     public  LoginInfo2(String username, String password) {
         this .userName = username;
         this .password = password;
         loginDate =  new  Date();
     }
     public  String toString() {
         return  "UserName="  + userName +  ", Password=" 
                 + password +  ", LoginDate="  + loginDate;
     }
 
     @Override
     public  void  writeExternal(ObjectOutput out)  throws  IOException {
         System.out.println( "Externalizable.writeExternal(ObjectOutput out) is called" );
         out.writeObject(loginDate);
         out.writeUTF(userName);
     }
 
     @Override
     public  void  readExternal(ObjectInput in)  throws  IOException, ClassNotFoundException {
         System.out.println( "Externalizable.readExternal(ObjectInput in) is called" );
         loginDate = (Date)in.readObject();
         userName = (String)in.readUTF();
     }
}

    测试类除了类名使用LoginInfo2以外,其他保持不变。下面是执行结果:

?
1
2
3
4
5
6
7
UserName=name, Password= 123 , LoginDate=Wed Nov  04  16 : 36 : 39  CST  2015
Serialize object
Externalizable.writeExternal(ObjectOutput out) is called
Deserialize object.
LoginInfo Constructor  //-------------------------Note this line
Externalizable.readExternal(ObjectInput in) is called
UserName=name, Password= null , LoginDate=Wed Nov  04  16 : 36 : 39  CST  2015

    可以看到,反序列化后的Password一项依然为null。

    需要注意的是:对于恢复Serializable对象,对象完全以它存储的二进制为基础来构造,而不调用构造器。而对于一个Externalizable对象,public的无参构造器将会被调用(因此你可以看到上面的测试结果中有LoginInfoConstructor这一行),之后再调用readExternal()方法。在Externalizable接口文档中,也给出了相关描述:

When an Externalizable object is reconstructed, an instance is created using the public no-arg constructor, then the readExternal method called. Serializable objects are restored by reading them from an ObjectInputStream.

    如果没有发现public的无参构造器,那么将会报错。(把LoginInfo2类的无参构造器注释掉,就会产生错误了)

?
1
2
3
4
5
6
7
8
9
10
UserName=name, Password= 123 , LoginDate=Wed Nov  04  17 : 03 : 24  CST  2015
Serialize object
Deserialize object.
Exception in thread  "main"  java.io.InvalidClassException: LoginInfo2; no valid constructor
     at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java: 150 )
     at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java: 768 )
     at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java: 1772 )
     at java.io.ObjectInputStream.readObject0(ObjectInputStream.java: 1350 )
     at java.io.ObjectInputStream.readObject(ObjectInputStream.java: 370 )
     at Test2.main(Test2.java: 19 )

    那么,如果把Externalizable接口和transient关键字一起用,会是什么效果呢?我们在LoginInfo2中的password加上关键字transient,再修改writeExternal()和readExternal()方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
     @Override
     public  void  writeExternal(ObjectOutput out)  throws  IOException {
         out.writeObject(loginDate);
         out.writeUTF(userName);
         out.writeUTF(password); //强行将transient修饰的password属性也序列化进去
     }
 
     @Override
     public  void  readExternal(ObjectInput in)  throws  IOException, ClassNotFoundException {
         loginDate = (Date)in.readObject();
         userName = (String)in.readUTF();
         password = (String)in.readUTF(); //反序列化password字段
     }

    执行结果:

?
1
2
3
4
5
UserName=name, Password= 123 , LoginDate=Wed Nov  04  16 : 58 : 27  CST  2015
Serialize object
Deserialize object.
LoginInfo Constructor
UserName=name, Password= 123 , LoginDate=Wed Nov  04  16 : 58 : 27  CST  2015

    从结果中可以看到,尽管在password字段上使用了transient关键字,但是这还是没能阻止被序列化。因为不是以Serializable方式去序列化和反序列化的。也就是说:transient关键字只能与Serializable接口搭配使用

目录
相关文章
|
4天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
1月前
|
Java 开发者
在 Java 中,一个类可以实现多个接口吗?
这是 Java 面向对象编程的一个重要特性,它提供了极大的灵活性和扩展性。
148 57
|
4天前
|
缓存 安全 Java
Java volatile关键字:你真的懂了吗?
`volatile` 是 Java 中的轻量级同步机制,主要用于保证多线程环境下共享变量的可见性和防止指令重排。它确保一个线程对 `volatile` 变量的修改能立即被其他线程看到,但不能保证原子性。典型应用场景包括状态标记、双重检查锁定和安全发布对象等。`volatile` 适用于布尔型、字节型等简单类型及引用类型,不适用于 `long` 和 `double` 类型。与 `synchronized` 不同,`volatile` 不提供互斥性,因此在需要互斥的场景下不能替代 `synchronized`。
2061 3
|
7天前
|
数据采集 JSON Java
利用Java获取京东SKU接口指南
本文介绍如何使用Java通过京东API获取商品SKU信息。首先,需注册京东开放平台账号并创建应用以获取AppKey和AppSecret。接着,查阅API文档了解调用方法。明确商品ID后,构建请求参数并通过HTTP客户端发送请求。最后,解析返回的JSON数据提取SKU信息。注意遵守API调用频率限制及数据保护法规。此方法适用于电商平台及其他数据获取场景。
|
12天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
43 6
|
29天前
|
Java API
Java中内置的函数式接口
Java中内置的函数式接口
25 2
|
1月前
|
Java
在Java中,接口之间可以继承吗?
接口继承是一种重要的机制,它允许一个接口从另一个或多个接口继承方法和常量。
97 1
|
Java
Java接口和抽象类
Java接口和抽象类
93 0
|
4月前
|
设计模式 Java
【惊天揭秘】Java编程绝技大曝光:接口、抽象类、静态类与非静态类的神秘面纱终被揭开!
【8月更文挑战第22天】Java支持面向对象编程,通过接口、抽象类、静态类(如枚举与工具类)及普通类实现设计原则。接口定义行为规范,允许多重继承;抽象类含未实现的抽象方法,需子类完成;静态类常为工具类,提供静态方法;普通类则实例化对象。恰当运用这些结构能提升程序质量。
44 2
|
7月前
|
设计模式 搜索推荐 Java
java接口和抽象类的区别,以及使用选择
java接口和抽象类的区别,以及使用选择
83 0