对象序列化

对象序列化的目标是将对象保存到磁盘中,或允许网络中直接传输对象。对象序列化机制允许把内存中的对象转换成平台无关的二进制流,从而允许这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点,其它程序一旦获得这种二进制流,都可以将这种二进制流恢复为原来的Java对象。

对象的序列化指将一个Java对象写入IO流中,与此对应,对象的反序列化指从IO流中恢复该Java对象。
如果需要让对象支持序列化机制,则必须让它的类是可序列化的。为了让某个类是可序列化的,该类必须实现如下两个接口之一。

  • Serializable
  • Externalizable
    其中Serializable是一个标记接口,实现该接口无须实现任何方法,它只是表明该类的实例是可序列化的。

使用对象流实现序列化

一旦某个类实现了Serializable接口,该类的对象就是可序列化的,程序可以通过如下两个步骤来序列化该对象。

  1. 创建ObjectOutputStream流
  2. 调用writeObject(Object obj)方法
public class Person implements Serializable {
    private static final long serialVersionUID = 3232557182439996130L;
    private String name;
    private int age;

    public Person(String name, int age) {
        System.out.println("有参构造器");
        this.name=name;
        this.age=age;
    }

    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"))) {
            Person per = new Person("sunqiang", 30);
            oos.writeObject(per);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

对象引用的序列化

  • 如果某个类的成员变量的类型不是基本类型或String类型,那么这个引用类必须是可序列化的,否则拥有该类型成员变量的类也是不可序列化的。
  • Java序列化机制采用了一种特殊的序列化算法,所有保存到磁盘中的对象都有一个序列化序号。当程序试图序列化一个对象时,程序先检查该对象是否已经被序列化,只有该对象从未被序列化过,系统才将该对象转化成字节序列并输出,如果某个对象已经序列化过,程序将只是直接输出一个序列化编号,而不是再次重新序列化该对象。
  • 由于Java序列化机制,如果多次序列化同一个Java对象,只有第一次才会把该Java对象转换成字节序列并输出,这样可能存在一个潜在的问题,如下:
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.txt"))) 
        {
            Person person = new Person("sun", 500);
            Teacher t1 = new Teacher("li", person);

            oos.writeObject(t1);
            t1.setName("改变姓名");
            oos.writeObject(t1);


        } catch (IOException ex) {
            ex.printStackTrace();
        }

两次将t1对象序列化,但在第二次序列化前改变了t1变量的值,因为t1已经被序列化过一次,再次调用writeObject()不会将该对象写入,下面读取对象,可以看到两次读取的对象name属性相同。

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("teacher.txt"))) {
            Teacher t1 = (Teacher) ois.readObject();
            Teacher t2 = (Teacher) ois.readObject();
            System.out.println("t1:name:" + t1.getName());
            System.out.println("t2:name:" + t2.getName());
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (ClassNotFoundException cnf) {
            cnf.printStackTrace();
        }

自定义序列化

transient关键字

通过在实例变量前使用transient关键字,可以指定Java序列化时无须理会该实例变量。

自定义序列化

使用transient修饰的变量被隔离在序列化机制之外,这样导致在反序列化恢复Java对象是无法获取该实例变量的值。Java还提供了一种自定义序列化机制,通过这种机制可以让程序控制如何序列化各实例变量。
有一个Person类:

public class Person implements Serializable {
    private static final long serialVersionUID = -4271096500079406727L;
    private String name;
    private int age;
    //省略getter,setter方法
}

方法1

为序列化类提供如下方法:

 private void writeObject(java.io.ObjectOutputStream out)
     throws IOException
 private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;
 private void readObjectNoData()
     throws ObjectStreamException;

下面是一个例子

private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }
    private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException {
        this.name=((StringBuffer)in.readObject()).reverse().toString();
        this.age=in.readInt();
    }

方法2

为序列化类提供如下方法:

 ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

writeReplace由序列化机制调用,只要该方法存在,Java在序列化机制运行保证在序列化某个对象之前,先调用该对象的writeReplace()方法。

private Object writeReplace(){
        ArrayList<Object> list=new ArrayList<>();
        list.add(name);
        list.add(age);
        return list;
    }
    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("replace.txt"));
            ObjectInputStream ois=new ObjectInputStream(new FileInputStream("replace.txt"))){
                Person person=new Person("sun",550);
                oos.writeObject(person);
                ArrayList list=(ArrayList)ois.readObject();
                System.out.println(list);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

方法3

为序列化类提供如下方法:

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

实现Externalizable接口自定义序列化

要实现该接口里的两个方法:

@Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.name = ((StringBuffer) in.readObject()).reverse().toString();
        this.age = in.readInt();
    }

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

注意

关于对象序列化,还要注意一下几点:

  • 对象的类名、实例变量都会被序列化;方法、类变量,transient实例变量都不会被序列化。
  • 保证序列化对象的实例变量也是可序列化的,否则需要使用transient关键字来修饰该实例变量,要不然,该类就是不可序列化的。
  • 反序列化对象时必须有序列化对象的class文件
  • 当通过文件、网络来读取序列化后的对象时,必须按实际写入的顺序读取。

猜你喜欢

转载自www.cnblogs.com/sqmax/p/9045376.html