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 的函数式接口和 Lambda 表达式
【4月更文挑战第19天】Java 中的函数式接口和 Lambda 表达式提供了简洁、灵活的编程方式。函数式接口有且仅有一个抽象方法,用于与 Lambda(一种匿名函数语法)配合,简化代码并增强可读性。Lambda 表达式的优点在于其简洁性和灵活性,常用于事件处理、过滤和排序等场景。使用时注意兼容性和变量作用域,它们能提高代码效率和可维护性。
|
5天前
|
Java
Java接口中可以定义哪些方法?
【4月更文挑战第13天】
6 0
Java接口中可以定义哪些方法?
|
6天前
|
设计模式 Java
Java接口与抽象类
Java接口与抽象类
15 0
|
9天前
|
存储 Java
Java输入输出:解释一下序列化和反序列化。
Java中的序列化和反序列化是将对象转换为字节流和反之的过程。ObjectOutputStream用于序列化,ObjectInputStream则用于反序列化。示例展示了如何创建一个实现Serializable接口的Person类,并将其序列化到文件,然后从文件反序列化回Person对象。
17 5
|
10天前
|
安全 Java 编译器
接口之美,内部之妙:深入解析Java的接口与内部类
接口之美,内部之妙:深入解析Java的接口与内部类
33 0
接口之美,内部之妙:深入解析Java的接口与内部类
|
13天前
|
存储 Java
java接口和内部类
java接口和内部类
|
14天前
|
缓存 安全 Java
Java中函数式接口详解
Java 8引入函数式接口,支持函数式编程。这些接口有单一抽象方法,可与Lambda表达式结合,简化代码。常见函数式接口包括:`Function<T, R>`用于转换操作,`Predicate<T>`用于布尔判断,`Consumer<T>`用于消费输入,`Supplier<T>`用于无参生成结果。开发者也可自定义函数式接口。Lambda表达式使实现接口更简洁。注意异常处理和线程安全。函数式接口广泛应用于集合操作、并行编程和事件处理。提升代码可读性和效率,是现代Java开发的重要工具。
26 0
|
1月前
|
存储 C#
C#中的序列化和反序列化
C#中的序列化和反序列化
12 0
|
1月前
|
存储 Java 数据库
|
3月前
|
Go
golang力扣leetcode 297.二叉树的序列化与反序列化
golang力扣leetcode 297.二叉树的序列化与反序列化
24 0

热门文章

最新文章