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接口搭配使用

目录
相关文章
|
8天前
|
缓存 Java 程序员
深入理解Java关键字volatile(上)
深入理解Java关键字volatile
16 0
|
6天前
|
存储 Java
java IO接口(Input)用法
【5月更文挑战第1天】Java的`java.io`包包含多种输入输出类。此示例展示了如何使用`FileInputStream`从`input.txt`读取数据。首先创建`FileInputStream`对象,接着创建一个字节数组存储读取的数据,调用`read()`方法将文件内容填充至数组。然后将字节数组转换为字符串并打印,最后关闭输入流。注意,`InputStream`是抽象类,此处使用其子类`FileInputStream`。其他子类如`ByteArrayInputStream`、`ObjectInputStream`和`BufferedInputStream`各有特定用途。
17 2
|
6天前
|
NoSQL Java API
java一行代码实现RESTFul接口
Spring Data REST是构建在Spring Data之上的库,可自动将repository转换为REST服务,支持JPA、MongoDB、Neo4j、GemFire和Cassandra。无需手动创建Service和Controller层。要开始,需配置JPA数据源,创建实体类和Repository接口。快速实现REST接口,只需引入spring-boot-starter-data-rest Maven依赖,并在Repository接口上添加@RepositoryRestResource注解。
|
7天前
|
存储 安全 Java
【亮剑】Java并发编程中的四个关键字:ThreadLocal、Volatile、Synchronized和Atomic
【4月更文挑战第30天】Java并发编程涉及`ThreadLocal`、`Volatile`、`Synchronized`和`Atomic`四个关键机制。`ThreadLocal`为每个线程提供独立变量副本;`Volatile`确保变量可见性,但不保证原子性;`Synchronized`实现同步锁,保证单线程执行;`Atomic`类利用CAS实现无锁并发控制。理解其原理有助于编写高效线程安全代码。根据业务场景选择合适机制至关重要。
|
7天前
|
Java 编译器
【Java探索之旅】this 关键字 解决你的成员变量困惑
【Java探索之旅】this 关键字 解决你的成员变量困惑
15 0
|
7天前
|
Java
【Java探索之旅】我与Java的初相识(完):注释,标识符,关键字
【Java探索之旅】我与Java的初相识(完):注释,标识符,关键字
5 0
|
7天前
|
Java 程序员 数据格式
关于Java抽象类和接口的总结和一点个人的看法
关于Java抽象类和接口的总结和一点个人的看法
|
8天前
|
Java
Java里的关键字 __final
Java里的关键字 __final
|
8天前
|
缓存 Java
深入理解Java关键字volatile(下)
深入理解Java关键字volatile(下)
6 0
|
2月前
|
存储 C#
C#中的序列化和反序列化
C#中的序列化和反序列化
12 0