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

目录
相关文章
|
1天前
|
存储 安全 Java
[Java基础面试题] Map 接口相关
[Java基础面试题] Map 接口相关
|
2天前
|
安全 Java 编译器
是时候来唠一唠synchronized关键字了,Java多线程的必问考点!
本文简要介绍了Java中的`synchronized`关键字,它是用于保证多线程环境下的同步,解决原子性、可见性和顺序性问题。从JDK1.6开始,synchronized进行了优化,性能得到提升,现在仍可在项目中使用。synchronized有三种用法:修饰实例方法、静态方法和代码块。文章还讨论了synchronized修饰代码块的锁对象、静态与非静态方法调用的互斥性,以及构造方法不能被同步修饰。此外,通过反汇编展示了`synchronized`在方法和代码块上的底层实现,涉及ObjectMonitor和monitorenter/monitorexit指令。
11 0
|
2天前
|
Java
两千字讲明白java中instanceof关键字的使用!
两千字讲明白java中instanceof关键字的使用!
9 0
|
2天前
|
Java 开发者
Java基础知识整理,注释、关键字、运算符
在日常的工作中,总会遇到很多大段的代码,逻辑复杂,看得人云山雾绕,这时候若能言简意赅的加上注释,会让阅读者豁然开朗,这就是注释的魅力!
37 11
|
7天前
|
Java 开发者
探索 Java 的函数式接口和 Lambda 表达式
【4月更文挑战第19天】Java 中的函数式接口和 Lambda 表达式提供了简洁、灵活的编程方式。函数式接口有且仅有一个抽象方法,用于与 Lambda(一种匿名函数语法)配合,简化代码并增强可读性。Lambda 表达式的优点在于其简洁性和灵活性,常用于事件处理、过滤和排序等场景。使用时注意兼容性和变量作用域,它们能提高代码效率和可维护性。
|
7天前
|
安全 Java 开发者
Java并发编程:深入理解Synchronized关键字
【4月更文挑战第19天】 在Java多线程编程中,为了确保数据的一致性和线程安全,我们经常需要使用到同步机制。其中,`synchronized`关键字是最为常见的一种方式,它能够保证在同一时刻只有一个线程可以访问某个对象的特定代码段。本文将深入探讨`synchronized`关键字的原理、用法以及性能影响,并通过具体示例来展示如何在Java程序中有效地应用这一技术。
|
7天前
|
Java
Java接口中可以定义哪些方法?
【4月更文挑战第13天】
14 0
Java接口中可以定义哪些方法?
|
9天前
|
设计模式 Java
Java接口与抽象类
Java接口与抽象类
17 0
|
1月前
|
存储 C#
C#中的序列化和反序列化
C#中的序列化和反序列化
12 0
|
1月前
|
存储 Java 数据库