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

目录
相关文章
|
5天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
23 4
|
11天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
8天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
37 5
|
10天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
18 3
|
13天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
9天前
|
Java
Java基础(13)抽象类、接口
本文介绍了Java面向对象编程中的抽象类和接口两个核心概念。抽象类不能被实例化,通常用于定义子类的通用方法和属性;接口则是完全抽象的类,允许声明一组方法但不实现它们。文章通过代码示例详细解析了抽象类和接口的定义及实现,并讨论了它们的区别和使用场景。
|
10天前
|
Java 测试技术 API
Java零基础-接口详解
【10月更文挑战第19天】Java零基础教学篇,手把手实践教学!
16 1
|
12天前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
10 0
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
2天前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。