1、如果某个成员变量是敏感信息,不希望序列化到文件/网络节点中,比如说银行密码,或者该成员变量所属的类是不可序列化的,

可以用 transient 关键字修饰此成员变量,序列化时会忽略此成员变量。

 class VipUser{
private int id;
private String name;
//序列化时,此成员变量不会写入到磁盘文件/网络节点中。
private transient String password;
//如果User类是不可序列化的,用transient修饰,序列化时会忽略这个成员变量,不会抛出异常
private transient User user;
//.......
}

transient只能修饰成员变量。

2、transient很方便,但是反序列化时,不能取得此成员变量的值。

如果要保证序列化后某些成员变量是安全的,不会被别人识别、破解,在反序列化时也可以读取该成员变量的值,这就需要自定义序列化了。

自定义序列化的类需要继承Serializable接口,并要写三个方法:

  • private  void  writeObject(ObjectOutputStream  out)     //序列化时会调用此方法
  • private  void  readObject(ObjectInputStream  in)      //反序列化时会调用此方法
  • private  void  readObjectNoData()     //当序列化的类版本和反序列化的类版本不同时,或者ObjectInputStream流被修改时,会调用此方法。

Serializable接口中没有任何成员,上面3个方法都是我们自己写的。

三个方法都是可选的,不强制,但一般都要实现前2个方法。

示例:

 //implements Serializable
class User implements Serializable{
private int id;
private String name;
private String password;
//......其他成员变量 public User(int id,String name,String password){
this.id=id;
this.name=name;
this.password=password;
} public int getId() {
return id;
} public String getName() {
return name;
} public String getPassword() {
return password;
} //自定义序列化
private void writeObject(ObjectOutputStream out) throws IOException {
//只序列化以下3个成员变量
out.writeInt(id);
out.writeObject(name);
//写入反序后的密码,当然我们也可以使用其他加密方式。这样别人打开文件,看到的就不是真正的密码,更安全。
out.writeObject(new StringBuffer(password).reverse());
} //自定义反序列化。注意:read()的顺序要和write()的顺序一致。比如说序列化时写的顺序是id、name、password,反序列化时读的顺序也要是id、name、password。
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
this.id=in.readInt();
//readObject()返回的是Object,要强制类型转换
this.name=(String)in.readObject();
//反序才得到真正的密码
StringBuffer pwd=(StringBuffer)in.readObject();
this.password=pwd.reverse().toString();
} }
 public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User zhangsan=new User(1,"张三","1234"); //序列化
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("./obj.txt"));
//调用我们自定义的writeObject()方法
out.writeObject(zhangsan);
out.close(); //反序列化
ObjectInputStream in=new ObjectInputStream(new FileInputStream("./obj.txt"));
//调用自定义的readObject()方法
User user=(User)in.readObject(); //写掉了一句 in.close() //测试
System.out.println(user.getId()); //1
System.out.println(user.getName()); //张三
System.out.println(user.getPassword()); //1234
}
}

3、我们甚至可以偷梁换柱,替换序列化的整个对象。

不写上面提到的3个方法,而是写writeReplace()方法:

private/default/protected/public  Object  writeReplace(){................}

访问权限可以是任意的。 返回一个Object类型的对象。

序列化时,会先自动调用writeReplace()方法,用返回的这个对象,替换要序列化的那个对象,再进行序列化。就是说,实际序列化的是writeReplace()返回的这个对象,并不是原来的对象。

示例:

 //implements Serializable
class User implements Serializable{
private int id;
private String name;
private String password;
//......其他成员变量 public User(int id,String name,String password){
this.id=id;
this.name=name;
this.password=password;
} public int getId() {
return id;
} public String getName() {
return name;
} public String getPassword() {
return password;
} //不用写writeObject()、readObject(),只写writeReplace()
private Object writeReplace(){
User lisi=new User(2,"李四","qwer");
return lisi;
} }
 public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User zhangsan=new User(1,"张三","1234"); //序列化
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("./obj.txt"));
//执行writeObject()时,会先自动调用writeReplace(),用返回的lisi替换原来的zhangsan,再序列化,实际序列化的对象是lisi
out.writeObject(zhangsan);
out.close(); //反序列化
ObjectInputStream in=new ObjectInputStream(new FileInputStream("./obj.txt"));
User user=(User)in.readObject(); //写掉了一句 in.close() //测试
System.out.println(user.getId()); //
System.out.println(user.getName()); //李四
System.out.println(user.getPassword()); //qwer
}
}

writeReplace()返回的是OBject,就是说任意类型均可。我们完全可以返回其他类型的对象,比如这样:

 private Object writeReplace(){
//返回字符串ok
return new String("ok");
}
 public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User zhangsan=new User(1,"张三","1234"); //序列化
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("./obj.txt"));
//会自动调用writeReplace(),用字符串ok替换对象zhangsan,实际序列化的对象字符串ok
out.writeObject(zhangsan);
out.close(); //反序列化
ObjectInputStream in=new ObjectInputStream(new FileInputStream("./obj.txt"));
String str=(String) in.readObject();
in.close(); //测试
System.out.println(str); //ok
}
}

当然,常见的做法是,对原对象的成员变量做一些加工处理。

 //implements Serializable
class User implements Serializable{
private int id;
private String name;
private String password; public User(int id,String name,String password){
this.id=id;
this.name=name;
this.password=password;
} //...... private Object writeReplace(){
String info="请编号为"+id+"的客户"+name+"到1号柜台办理业务。";
return info;
} }

4、我们也可以替换掉反序列化得到的整个对象。

private/default/protected/public  Object  readResolve(){................}

在反序列化读取对象后,会自动调用此方法,将读取的对象替换为指定的对象。

 //implements Serializable
class User implements Serializable{
private int id;
private String name;
private String password;
//......其他成员变量 public User(int id,String name,String password){
this.id=id;
this.name=name;
this.password=password;
} //........... //用指定的对象替换掉反序列化读取的对象
private Object readResolve(){
String info="请编号为"+id+"的客户"+name+"到1号柜台办理业务。";
return info;
} }
 public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User zhangsan=new User(1,"张三","1234"); //序列化
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("./obj.txt"));
out.writeObject(zhangsan);
out.close(); //反序列化
ObjectInputStream in=new ObjectInputStream(new FileInputStream("./obj.txt"));
String str=(String) in.readObject(); //读取的对象zhangsan会被替换为字符串info
in.close(); //测试
System.out.println(str); //请编号为1的客户张三到1号柜台办理业务。
}
}

4和3的使用方式是相同的,只是作用的时间点不同,可以联合使用。

虽然访问权限没有限制,但为了防止子类重写这些方法,造成不必要的麻烦,我们一般使用private修饰。

说明:

这些方法都是在要序列化的类中写的,只对这个类的对象起作用。

比如,我在User类中写了个writeReplace()方法,此方法只在序列化User类的对象时起作用,在序列化其他类的对象时,此方法是不起作用的。

5、之前介绍的方式都是implements  Serializable,我们也可以implement  Externalizable来实现序列化、反序列化。

要实现Externalizable接口里的2个抽象方法:

  • public void writeExternal(ObjectOutput  out)    //调用writeObject()时,会自动调用此方法来序列化对象
  • public void readExternal(ObjectInput  in)     //调用readObject()时,会自动调用此方法来反序列化

示例:

 //implements Externalizable
class User implements Externalizable{
private int id;
private String name;
private String password;
//......其他成员变量 //必须要有无参的构造函数
public User(){ } public User(int id,String name,String password){
this.id=id;
this.name=name;
this.password=password;
} public int getId() {
return id;
} public String getName() {
return name;
} public String getPassword() {
return password;
} //自定义序列化
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(id);
out.writeObject(name);
out.writeObject(password);
} //自定义反序列化。注意读的顺序要和写的顺序一致。
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.id=in.readInt();
this.name=(String)in.readObject();
this.password=(String)in.readObject();
}
}

implements  Serializable不要求要有无参的构造函数,但implements  Externalizable要求必须要有无参的构造函数,没有无参的构造函数,会报错。

因为反序列化时,要先自动调用无参的构造函数创建一个未初始化的对象,再自动调用readExternal()给这个对象的成员变量赋值,初始化这个对象,然后才返回这个对象。

有无参的构造函数,但没有在readExternal()中初始化某些成员变量,会正常返回该对象,只是对应的成员变量的值会使用默认的初始值,比如int是0,引用型的是null。

String、StringBuffer、StringBuilder都是引用型的。

6、2种序列化机制的比较

                                  实现Serializable接口                    实现Externalizable接口

如果未自定义序列化、反序列化方式,则系统会自动保存所有的成员变量。

如果自定义了序列化、反序列化方式,则按自定义的方式处理。

只能自定义序列化、反序列化方式,

必须实现2个抽象方法。

有没有无参的构造函数都行。 必须要有无参的构造函数。
性能略低。 性能略高

对象的类名、成员变量中的实例变量(基本类型+引用类型)都会被序列化。

transient、static修饰的成员变量都不会被序列化。如果只是要不能序列化这个效果,尽量用transient,虽然static可以达到相同的效果,但static不是为这个而设计的。

7、版本问题

反序列化时,必须要有该对象所属类的class文件。

序列化时JVM会记录class文件的版本id(唯一标识此版本的class文件,默认由JVM指定),反序列化时,会根据这个版本id找到对应版本的class文件,用这个class文件来进行反序列化,获得原来的对象。

如果序列化了对象,之后随着项目版本的升级,该class文件可能会升级,class文件的版本id可能会变化(新的class文件的版本id可能会由JVM重新指定)。之后反序列化时,JVM根据原来的版本id找不到对应版本的class文件来进行反序列化,就会报错。

解决方法:显示指定class文件的版本id。显示指定后,此class文件的所有版本都使用此版本id,不再由JVM指定。

 class User implements Serializable/Externalizable{
//固定写法,值可以是任意的long型值
private static final long serialVersionUID=512L;
//.......
}

Java 自定义序列化、反序列化的更多相关文章

  1. java自定义序列化

    自定义序列化 1.问题引出 在某些情况下,我们可能不想对于一个对象的所有field进行序列化,例如我们银行信息中的设计账户信息的field,我们不需要进行序列化,或者有些field本省就没有实现Ser ...

  2. java protostuff 序列化反序列化工具

    protostuff是由谷歌开发的一个非常优秀的序列化反序列化工具 maven导入包: <dependency> <groupId>io.protostuff</grou ...

  3. Java对象序列化/反序列化的注意事项(转)

    Java对象序列化 对于一个存在Java虚拟机中的对象来说,其内部的状态只是保存在内存中.JVM退出之后,内存资源也就被释放,Java对象的内部状态也就丢失了.而在很多情况下,对象内部状态是需要被持久 ...

  4. Java对象序列化/反序列化的注意事项

    Java对象序列化 对于一个存在Java虚拟机中的对象来说,其内部的状态只是保存在内存中.JVM退出之后,内存资源也就被释放,Java对象的内部状态也就丢失了.而在很多情况下,对象内部状态是需要被持久 ...

  5. java 自定义序列化

    pom.xml 导包 创建自己的序列化类,继承 com.fasterxml.jackson.databind.JsonSerializer<T> 抽象类 重写 serialize() 方法 ...

  6. java基础 序列化反序列化流 实现Serializable 接口 自动装载序列号到对象文本文件如修改不能反序列化对象文本,除非自定义long型常量 打印流

    package com.swift.baseKnowledge; import java.io.File; import java.io.FileInputStream; import java.io ...

  7. AXIS 1.4 自定义序列化/反序列化类

    axis1.4的CalendarDeserializer 使用的时区是GMT,导致日期显示不准确 private static SimpleDateFormat zulu = new SimpleDa ...

  8. Java对象序列化剖析

    对象序列化的目的 1)希望将Java对象持久化在文件中 2)将Java对象用于网络传输 实现方式 如果希望一个类的对象可以被序列化/反序列化,那该类必须实现java.io.Serializable接口 ...

  9. 深入分析Java的序列化与反序列化

    序列化是一种对象持久化的手段.普遍应用在网络传输.RMI等场景中.本文通过分析ArrayList的序列化来介绍Java序列化的相关内容.主要涉及到以下几个问题: 怎么实现Java的序列化 为什么实现了 ...

随机推荐

  1. fortran子程序传入可变数组要在module里实现

    坑死我了,我说怎么子程序传递不了可变数组 在写fortran程序的时候,要对矩阵实现特定的功能,如高斯法解线性方程组,很多时候子程序不知道矩阵的大小,如有限元程序中先要用程序得到总体刚度矩阵再把总刚传 ...

  2. day04_05 逻辑运算符、表达式

    num += 1 等价于 num = num + 1 逻辑运算符 and   全true则true 条件1 and 条件2 5>3 and 3>2   ===> true 5> ...

  3. Django创建并连接数据库(实现增删改查)--第二版

    注意点一: url里面的地址,不只是html页面,准确说是views视图里面对应的函数方法 <!DOCTYPE html> <html lang="en"> ...

  4. nyoj 题目2 括号配对问题

    描述 今天发现了nyoj,如获至宝.准备开刷. 括号配对问题 现在,有一行括号序列,请你检查这行括号是否配对.   输入 第一行输入一个数N(0<N<=100),表示有N组测试数据.后面的 ...

  5. 【Luogu】P2254瑰丽华尔兹(堆优化DP)

    题目链接 我也不知道为什么脑子一抽就想了个堆优化……然后贼慢…… 因为上午听不懂wys的电音专场(快速傅立叶变换),然后就做了这么一道题. 首先朴素DP很sb都能秒出.就是枚举时刻.位置(两维)然后转 ...

  6. 在 Ubuntu 16.04 上安装 Eclipse Oxygen

    2017 年 6 月 28 日,Eclipse 社区(the Eclipse Community)发布了 Eclipse Oxygen.本文记录了我在 Ubuntu 16.04 上安装 Eclipse ...

  7. ECMA-262 Extractions

    For the purpose of this article, ECMA-262 refers to ECMAScript® 2017 Language Specification. ECMAScr ...

  8. chromedriver对应的支持的Chrome版本(更新至Chrome64)

    很多网友在配置chromedriver的时候会遇到很多麻烦,在网上找了很多资料觉得这个表格不错,就给大家分享出来,希望对大家配置chrome的时候有帮助: chromedriver版本 支持的Chro ...

  9. [SDOI2013]直径 (树的直径,贪心)

    题目链接 Solution 我们直接找到一条直径 \(s\),起点为 \(begin\),终点为 \(end\). 从前往后遍历点 \(u\) ,若子树中最大的距离与 \(dis(u,begin)\) ...

  10. [转] 细说linux挂载——mount

    转载的文章不能分类 这点比较坑   暂时先发到随笔里了 标题会标注的 找到一篇写的对挂载比较好的文章  收藏ing 作者:adagio   Ubuntu社区 原帖网址:http://forum.ubu ...