zoukankan      html  css  js  c++  java
  • Java 自定义序列化、反序列化

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

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

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

    transient只能修饰成员变量。

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

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

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

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

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

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

    示例:

     1 //implements Serializable
     2 class User implements Serializable{
     3     private int id;
     4     private String name;
     5     private String password;
     6     //......其他成员变量
     7 
     8     public User(int id,String name,String password){
     9         this.id=id;
    10         this.name=name;
    11         this.password=password;
    12     }
    13 
    14     public int getId() {
    15         return id;
    16     }
    17 
    18     public String getName() {
    19         return name;
    20     }
    21 
    22     public String getPassword() {
    23         return password;
    24     }
    25 
    26     //自定义序列化
    27     private void writeObject(ObjectOutputStream out) throws IOException {
    28         //只序列化以下3个成员变量
    29         out.writeInt(id);
    30         out.writeObject(name);
    31         //写入反序后的密码,当然我们也可以使用其他加密方式。这样别人打开文件,看到的就不是真正的密码,更安全。
    32         out.writeObject(new StringBuffer(password).reverse());
    33     }
    34 
    35     //自定义反序列化。注意:read()的顺序要和write()的顺序一致。比如说序列化时写的顺序是id、name、password,反序列化时读的顺序也要是id、name、password。
    36     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    37         this.id=in.readInt();
    38         //readObject()返回的是Object,要强制类型转换
    39         this.name=(String)in.readObject();
    40         //反序才得到真正的密码
    41         StringBuffer pwd=(StringBuffer)in.readObject();
    42         this.password=pwd.reverse().toString();
    43     }
    44 
    45 }

     1 public class Test {
     2     public static void main(String[] args) throws IOException, ClassNotFoundException {
     3         User zhangsan=new User(1,"张三","1234");
     4 
     5         //序列化
     6         ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("./obj.txt"));
     7         //调用我们自定义的writeObject()方法
     8         out.writeObject(zhangsan);
     9         out.close();
    10 
    11         //反序列化
    12         ObjectInputStream in=new ObjectInputStream(new FileInputStream("./obj.txt"));
    13         //调用自定义的readObject()方法
    14         User user=(User)in.readObject();   //写掉了一句   in.close()
    15 
    16         //测试
    17         System.out.println(user.getId());   //1
    18         System.out.println(user.getName());   //张三
    19         System.out.println(user.getPassword());   //1234
    20     }
    21 }

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

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

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

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

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

    示例:

     1 //implements Serializable
     2 class User implements Serializable{
     3     private int id;
     4     private String name;
     5     private String password;
     6     //......其他成员变量
     7 
     8     public User(int id,String name,String password){
     9         this.id=id;
    10         this.name=name;
    11         this.password=password;
    12     }
    13 
    14     public int getId() {
    15         return id;
    16     }
    17 
    18     public String getName() {
    19         return name;
    20     }
    21 
    22     public String getPassword() {
    23         return password;
    24     }
    25 
    26     //不用写writeObject()、readObject(),只写writeReplace()
    27     private Object writeReplace(){
    28         User lisi=new User(2,"李四","qwer");
    29         return lisi;
    30     }
    31 
    32 }
     1 public class Test {
     2     public static void main(String[] args) throws IOException, ClassNotFoundException {
     3         User zhangsan=new User(1,"张三","1234");
     4 
     5         //序列化
     6         ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("./obj.txt"));
     7         //执行writeObject()时,会先自动调用writeReplace(),用返回的lisi替换原来的zhangsan,再序列化,实际序列化的对象是lisi
     8         out.writeObject(zhangsan);
     9         out.close();
    10 
    11         //反序列化
    12         ObjectInputStream in=new ObjectInputStream(new FileInputStream("./obj.txt"));
    13         User user=(User)in.readObject();   //写掉了一句  in.close()
    14 
    15         //测试
    16         System.out.println(user.getId());  //2
    17         System.out.println(user.getName());   //李四
    18         System.out.println(user.getPassword());  //qwer
    19     }
    20 }

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

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

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

     1 //implements Serializable
     2 class User implements Serializable{
     3     private int id;
     4     private String name;
     5     private String password;
     6 
     7     public User(int id,String name,String password){
     8         this.id=id;
     9         this.name=name;
    10         this.password=password;
    11     }
    12 
    13     //......
    14 
    15     private Object writeReplace(){
    16         String info="请编号为"+id+"的客户"+name+"到1号柜台办理业务。";
    17         return info;
    18     }
    19 
    20 }

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

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

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

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

    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()时,会自动调用此方法来反序列化

    示例:

     1 //implements Externalizable
     2 class User implements Externalizable{
     3     private int id;
     4     private String name;
     5     private String password;
     6     //......其他成员变量
     7 
     8     //必须要有无参的构造函数
     9     public User(){
    10 
    11     }
    12 
    13     public User(int id,String name,String password){
    14         this.id=id;
    15         this.name=name;
    16         this.password=password;
    17     }
    18 
    19     public int getId() {
    20         return id;
    21     }
    22 
    23     public String getName() {
    24         return name;
    25     }
    26 
    27     public String getPassword() {
    28         return password;
    29     }
    30 
    31     //自定义序列化
    32     @Override
    33     public void writeExternal(ObjectOutput out) throws IOException {
    34         out.writeInt(id);
    35         out.writeObject(name);
    36         out.writeObject(password);
    37     }
    38 
    39     //自定义反序列化。注意读的顺序要和写的顺序一致。
    40     @Override
    41     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    42         this.id=in.readInt();
    43         this.name=(String)in.readObject();
    44         this.password=(String)in.readObject();
    45     }
    46 }

    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指定。

    1 class User implements Serializable/Externalizable{
    2     //固定写法,值可以是任意的long型值
    3    private static final long serialVersionUID=512L;
    4    //.......
    5 }
  • 相关阅读:
    win7(64)未在本地计算机上注册“Microsoft.Jet.OLEDB.4.0”提供程序的解决办法
    很方便的工具——代码生成工具之Winform查询列表界面生成
    程序员十几个常用网站
    优秀程序员不得不知道的20个位运算技巧
    unset()索引数组
    git 撤销修改
    git 版本回退
    git 命令详解
    git多账户配置
    Git的.ssh文件夹的内容
  • 原文地址:https://www.cnblogs.com/chy18883701161/p/10928229.html
Copyright © 2011-2022 走看看