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 + "]"; } }
例子关键点:
- 声明Cat类实现了Serializable接口。是一个标示器,没有要实现的方法。
- 新建Cat对象。
- 新建字节流对象(FileOutputStream)进序列化对象保存在本地文件中。
- 新建ObjectOutputStream对象,调用writeObject方法序列化Cat对象。
- writeObject方法会执行两个工作:序列化对象,然后将序列化的对象写入文件中。
- 反序列化就是调用ObjectInputStream的readObject()方法。
- 异常处理和流的关闭动作要执行。
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;