【java基础】自定义序列化

源:https://www.cnblogs.com/-9527/p/5222715.html

    偶滴小看法:对序列化、反序列化过程进行自定义或保证单一、一致性,java本身提供了一些方法或接口,使用时直接定义实现自己的逻辑,或实现接口 写自己的逻辑;

   如果一个类里包含的某些实例变量是敏感信息,例如银行账户信息,这时不希望系统将该实例变量值进行实例化;或者某个实例变量的类型是不可序列化的,因此不希望对该实例变量进行递归实例化,以避免引发异常。

 

1、使用transient:指定java序列化时无须理会该实例变量

Person 类中的变量name、age,构造函数、get set:
//transient只能修饰实例变量,不可修饰java程序中的其他成分
private transient int age;

被transient修饰的实例变量被完全隔离在序列化机制外,so在反序列化返回java对象时无法取得该实例变量的值

      先序列化一个Person对象,然后再反序列化该Person对象,得到反序列化的Person对象后程序输出该对象的age实例变量值

public class TransientTest 
{
    public static void main(String[] args) 
    {
        try(
                //创建一个ObjectOutputStream输出流
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("transient.txt"));
                //创建一个ObjectInputStream输入流
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream("transient.txt")))
        {
            Person per = new Person("孙悟空",500);
            //系统将per对象转换成了字节序列输出
            oos.writeObject(per);
            Person p = (Person) ois.readObject();
            System.out.println(p.getAge()+""+p.getName());
        }
        catch(Exception ex)
        {
            ex.printStackTrace();
        }

    }

}

     java还提供了一种自定义序列化机制,通过这种自定义序列化机制可以让程序控制如何序列化各实例变量,甚至完全不序列化某些实例变量(与使用transient关键字的效果相同)。

 

2、writeObject 、 readObject、readObjectNoData

     writeObject()方法负责写入特定类的实例状态,以便相应的readObject()方法可以恢复它。通过重写该方法,可完全获得对序列化机制的控制,自主决定哪些实例变量需要序列化,需要怎样序列化。默认情况,该方法会调用out.defaultWriteObject来保存java 对象的各实例变量,从而可以实现序列化java对象状态的目的。

     readObject()方法负责从流中读取并恢复对象的实例变量,通过重写该方法,可完全获得对反序列化机制的控制,自主决定需要反序列化哪些实例变量,以及如何进行反序列化。默认情况下,该方法会调用in.defaultReadObject来恢复java对象的非瞬态实例变量。在通常情况下,readObject()方法与writeObject()方法对应,如果writeObject()方法中对java对象的实例变量进行了一些处理,则应该在readObject()方法中对其实例变量进行相应的反处理,以便正确恢复该对象。

    当序列化流不完整时,readObjectNoData()方法可以用来正确的初始化反序列化的对象。例如,接收方使用的反序列化类的版本不同于发送方,或者接收方版本扩展的类不是发送方版本扩展的类,或者序列化流被篡改时,系统都会调用readObjectNoData()方法来初始化反序列化对象。

person类中添加上面基础上:

private void writeObject(java.io.ObjectOutputStream out) throws IOException
    {
        //将name实例变量值反转后写入二进制流
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }
    
    private void readObject(java.io.ObjectInputStream in) throws Exception
    {
        //将读取的字符串反转后赋给name变量
        this.name = ((StringBuffer)in.readObject()).reverse().toString();
        this.age = in.readInt();
    }

 

3、writeReplace  readResolve

 1)、writeReplace

        writeReplace()方法将由序列化机制调用,只要该方法存在。因为该方法可以拥有私有(private),受保护的(protected),和包私有(package-private)等访问权限,所以其子类有可能获得该

Person类两个属性 get set 构造函数 下:
 
   //重写writeReplace方法,程序在序列化该对象之前,先调用该方法
    private Object writeReplace()
    {
        ArrayList<Object> list = new ArrayList<Object>();
        list.add(name);
        list.add(age);
        return list;
        
    }

     java的序列化机制保证在序列化某个对象之前,先调用该对象的writeReplace()方法,如果该方法返回另一个java对象,则系统转为序列化另一个对象。

//反序列化读取到的是ArrayList
 ArrayList list = (ArrayList)ois.readObject();

系统在序列化某个对象之前,会先调用该对象的writeReplace()和writeObject()两个方法,系统总是先调用被序列化对象的writeReplace()方法,如果该方法返回另一个对象,系统将再次调用另一个对象的writeReplace()方法,直到该方法不再返回另一个对象为止,程序最后将调用该对象的writeObject()方法来保存该对象的状态。

 

2)、readResolve

       readResolve实现保护性复制整个对象,该方法紧挨着readObject()之后被调用,该方法的返回值将会代替原来反序列化的对象,而原来readObject()反序列化的对象将会立即丢弃。

       readResolve()方法可使用任意的访问控制符,因此父类的readResolve()方法可能被其子类继承。这样利用readResolve()方法时就会存在一个明显的缺点,当父类已经实现了readResolve()方法后,子类将变得无从下手。如父类包含一个protected或public的readResolve()方法,而且子类也没有重写该方法,将会使得子类反序列化时得到一个父类的对象,这显然不是程序要的结果,而且也不容易发现这种错误。总是让子类重写readResolve()方法无疑是一个负担,因此对于要被作为父类继承的类而言,实现readResolve()方法可能有一些潜在的危险。

//保证反序列化得到的依然是Orientation的HORIZOHTAL或VERTICAL两个枚举值之一
private Object readResolve() throws ObjectStreamException
     {
        if(value == 1)
        {
            return HORIZONTAL;
        }
        if(value == 2)
        {
            return VERTICAL;
        }
         return null;     
     }

通常的建议是,对于final类重写readResolve()方法不会有任何问题:否则,重写readResolve()方法时应尽量使用private修饰该方法。所有单例类,枚举类在实现序列化时都应该提供readResolve()方法,这样才可以保证反序列化的对象依然正常。

4、实现Externalizable接口:由开发决定存储和恢复对象数据

两个方法:

      void readExternal(ObjectInput in): 需要序列化的类实现 该方法来实现反序列化。该方法调用DataInput(它是ObjectInput 的父接口) 的方法来恢复基本类型的实例变量值,调用ObjectInput的readObject()方法来恢复引用类型的实例变量值。

      void writeExternal(Object out): 需要序列化的类实现该方法来保存对象的状态。该方法调用DataOutput(它是ObjectOutput 的父接口)的方法来保存基本类型的实例变量值,调用ObjectOutput的writeObject()方法来保存引用类型的实例变量值。

    当使用Externalizable机制反序列化该对象时,程序会使用public的无参构造器创建实例,然后才执行readExternal()方法进行反序列化,因此实现Externalizable的序列化类必须提供public的无参构造

     能带来一定的性能提升,但导致编程复杂度的增加,所以大部分时候都是采用实现Serializable接口方式来实现序列化。

源:https://www.cnblogs.com/-9527/p/5222715.html

再写一点拷贝:【

       对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去。

       深复制和浅复制最根本的区别在于是否是真正获取了一个对象的复制实体,而不是引用。

浅复制

       只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用;换句话说,浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。

深复制 :

        在计算机中开辟了一块新的内存地址用于存放复制的对象。不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上。

C++中:【】感觉还蛮相似滴

浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间;

深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针;

猜你喜欢

转载自blog.csdn.net/ma15732625261/article/details/81304307