110.Java对象的序列化

对象的序列化

当创建对象时,程序运行时它就会存在,但是程序停止时,对象也就消失了.但是如果希望对象在程序不运行的情况下仍能存在并保存其信息,将会非常有用,对象将被重建并且拥有与程序上次运行时拥有的信息相同。可以使用对象的序列化。

 对象的序列化:   将内存中的对象直接写入到文件设备中

 对象的反序列化: 将文件设备中持久化的数据转换为内存对象

基本的序列化由两个方法产生:一个方法用于序列化对象并将它们写入一个流,另一个方法用于读取流并反序列化对象。

ObjectOutput
writeObject(Object obj) 
          将对象写入底层存储或流。
ObjectInput
readObject() 
          读取并返回对象。

 ObjectOutputStream

 ObjectInputStream

由于上述ObjectOutput和ObjectInput是接口,所以需要使用具体实现类。

ObjectOutput
 ObjectOutputStream被写入的对象必须实现一个接口:Serializable
否则会抛出:NotSerializableException 
ObjectInput
     ObjectInputStream    该方法抛出异常:ClassNotFountException     

ObjectOutputStream和ObjectInputStream 对象分别需要字节输出流和字节输入流对象来构建对象。也就是这两个流对象需要操作已有对象将对象进行本地持久化存储。

 

案例:

序列化和反序列化Cat对象。

public class Demo3 {
    public static void main(String[] args) throws IOException,
            ClassNotFoundException {
        Cat cat = new Cat("tom", 3);
        FileOutputStream fos = new FileOutputStream(new File("c:\\Cat.txt"));
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(cat);
        System.out.println(cat);
        oos.close();
        // 反序列化
        FileInputStream fis = new FileInputStream(new File("c:\\Cat.txt"));
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object readObject = ois.readObject();
        Cat cat2 = (Cat) readObject;
        System.out.println(cat2);
        fis.close();
}

class Cat implements Serializable {
    public String name;
    public int age;

    public Cat() {

    }

    public Cat(String name, int age) {

        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Cat [name=" + name + ", age=" + age + "]";
    }

}

例子关键点:

  1. 声明Cat类实现了Serializable接口。是一个标示器,没有要实现的方法。
  2. 新建Cat对象。
  3. 新建字节流对象(FileOutputStream)进序列化对象保存在本地文件中。
  4. 新建ObjectOutputStream对象,调用writeObject方法序列化Cat对象。
  5. writeObject方法会执行两个工作:序列化对象,然后将序列化的对象写入文件中。
  6. 反序列化就是调用ObjectInputStream的readObject()方法。
  7. 异常处理和流的关闭动作要执行。

Serializable:

类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

所以需要被序列化的类必须是实现Serializable接口,该接口中没有描述任何的属性和方法,称之为标记接口。

如果对象没有实现接口Serializable,在进行序列化时会抛出:NotSerializableException 异常。

注意:

保存一个对象的真正含义是什么?如果对象的实例变量都是基本数据类型,那么就非常简单。但是如果实例变量是包含对象的引用,会怎么样?保存的会是什么?很显然在Java中保存引用变量的实际值没有任何意义,因为Java引用的值是通过JVM的单一实例的上下文中才有意义。通过序列化后,尝试在JVM的另一个实例中恢复对象,是没有用处的。

如下:

首先建立一个Dog对象,也建立了一个Collar对象。Dog中包含了一个Collar(项圈)

现在想要保存Dog对象,但是Dog中有一个Collar,意味着保存Dog时也应该保存Collar。假如Collar也包含了其他对象的引用,那么会发生什么?意味着保存一个Dog对象需要清楚的知道Dog对象的内部结构。会是一件很麻烦的事情。

   Java的序列化机制可以解决该类问题,当序列化一个对象时,Java的序列化机制会负责保存对象的所有关联的对象(就是对象图),反序列化时,也会恢复所有的相关内容。本例中:如果序列化Dog会自动序列化Collar。但是,只有实现了Serializable接口的类才可以序列化。如果只是Dog实现了该接口,而Collar没有实现该接口。会发生什么?

Dog类和Collar类

import java.io.Serializable;

public class Dog implements Serializable {
    private Collar collar;
    private String name;

    public Dog(Collar collar, String name) {

        this.collar = collar;
        this.name = name;
    }

    public Collar getCollar() {
        return collar;
    }

}

class Collar {
    private int size;

    public int getSize() {
        return size;
    }

    public Collar(int size) {
        this.size = size;
    }

}

序列化

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Demo4 {
    public static void main(String[] args) throws IOException {
        Collar coll = new Collar(10);
        Dog dog = new Dog(coll, "旺财");

        FileOutputStream fis = new FileOutputStream(new File("c:\\dog.txt"));
        ObjectOutputStream os = new ObjectOutputStream(fis);
        os.writeObject(dog);
    }
}

执行程序,出现了运行时异常。

Exception in thread "main" java.io.NotSerializableException: Collar

所以我们也必须将Dog中使用的Collar序列化。但是如果我们无法访问Collar的源代码,或者无法使Collar可序列化,如何处理?

两种解决方法:

一:继承Collar类,使子类可序列化

但是:如果Collar是final类,就无法继承了。并且,如果Collar引用了其他非序列化对象,也无法解决该问题。

transient

此时就可以使用transient修饰符,可以将Dog类中的成员变量标识为transient

那么在序列化Dog对象时,序列化就会跳过Collar。

 

public class Demo4 {
    public static void main(String[] args) throws IOException,
            ClassNotFoundException {
        Collar coll = new Collar(10);
        Dog dog = new Dog(coll, "旺财");
        System.out.println(dog.getCollar().getSize());

        FileOutputStream fis = new FileOutputStream(new File("c:\\dog.txt"));
        ObjectOutputStream os = new ObjectOutputStream(fis);
        os.writeObject(dog);

        // 反序列化
        FileInputStream fos = new FileInputStream(new File("c:\\dog.txt"));
        ObjectInputStream ois = new ObjectInputStream(fos);
        Object readObject = ois.readObject();
        Dog dog2 = (Dog) readObject;
        // Collar未序列化。
        dog2.getCollar().getSize();
    }
}

这样我们具有一个序列化的Dog和非序列化的Collar。

此时反序列化Dog后,访问Collar,就会出现运行时异常

10
Exception in thread "main" java.lang.NullPointerException

注意:序列化不适用于静态变量,因为静态变量并不属于对象的实例变量的一部分。静态变量随着类的加载而加载,是类变量。由于序列化只适用于对象。

 

基本数据类型可以被序列化

public class Demo5 {
    public static void main(String[] args) throws IOException {
        // 创建序列化流对象
        FileOutputStream fis = new FileOutputStream(new File("c:\\basic.txt"));
        ObjectOutputStream os = new ObjectOutputStream(fis);
        // 序列化基本数据类型
        os.writeDouble(3.14);
        os.writeBoolean(true);
        os.writeInt(100);
        os.writeInt(200);

        // 关闭流
        os.close();

        // 反序列化
        FileInputStream fos = new FileInputStream(new File("c:\\basic.txt"));
        ObjectInputStream ois = new ObjectInputStream(fos);

        System.out.println(ois.readDouble());
        System.out.println(ois.readBoolean());
        System.out.println(ois.readInt());
        System.out.println(ois.readInt());

        fos.close();
    }
}

serialVersionUID

用于给类指定一个UID。该UID是通过类中的可序列化成员的数字签名运算出来的一个long型的值。

只要是这些成员没有变化,那么该值每次运算都一样。

该值用于判断被序列化的对象和类文件是否兼容。

如果被序列化的对象需要被不同的类版本所兼容。可以在类中自定义UID。

定义方式:static final long serialVersionUID = 42L;

posted @ 2020-09-14 09:56  nohert  阅读(221)  评论(0编辑  收藏  举报