Serializable
JAVA序列化详解(串行化)-Serializable接口
假设你想保存一个或多个对象的状态。如果Java不具有序列化功能,就不得不使用某个I/O类写出你想保存的所有对象的实例变量的状态。最糟糕的事情是尝试重构一个新对对象,而且该对象实际上与你正尝试保存的对象完全相同。此时,你需要自己的协议,来写出和恢复每个对象的状态,或者你可以用错误的值来终止设置变量。例如,假设你存储了一个对象,它具有用于表示高度和宽度的实例变量。在你存储对象的状态时,可以把高度和宽度作为两个int写出到一个文件中,但是,你写出它们的次序是至关重要的。它实在太容易了,以至于不会重建对象,但是会混合高度和宽度值——把保存的高度作为新对象的宽度值,反之亦然。
序列化允许简单地说“保存这个对象及其所有实例变量”。实际上,更有趣的是,由于你可以增加“……除非我显示地将一个变量标识为transient,这意味着,不会把这个瞬态变量的值作为对象的序列化状态的一部分包括进来”。
1、使用ObjectOutputStream和ObjectInputStream
基本序列化的神奇能力只是由两个方法产生的:一个方法用于序列化对象并把它们写到一个流,第二个方法用于读取流 反序列化对象。
ObjectOutputStream.writeObject(); //serialize and write
ObjectInputStream.readObject(); //read and deserialize
java.io.ObjectOutputStream和java.io.ObjectInputStream类被认为是java.io包中的高级类,这意味着你将把它们包围在低级类的周围,如java.io.FileOutputStream和java.io.FileInputStream。下面是一个小程序,它将创建一个(Cat)对象,序列化它,然后反序列化它:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializeCat {
public static void main(String[] args) {
Cat c = new Cat();
try {
FileOutputStream fs = new FileOutputStream("testSer.ser");
ObjectOutputStream os = new ObjectOutputStream(fs);
os.writeObject(c);
os.close();
}
catch(Exception e) {
e.printStackTrace();
}
try {
FileInputStream fis = new FileInputStream("testSer.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
c = (Cat)ois.readObject();
ois.close();
}
catch(Exception e) {
e.printStackTrace();
}
}
}
class Cat implements Serializable {}
让我们看看这个示例中的几个关键点:
(1)我们声明Cat类实现了Serializable接口。Serializable接口是一个标识接口;它没有要实现的方法(在后面的几节中我们将介绍关于何时需要声明Serializable类的各种规则)。
(2)建立一个Cat对象,我们知道它是可序列化的。
(3)通过调用writeObject()方法序列化Cat对象c。在我们可以实际地序列化Cat之前,有大量的准备工作要做。首先,我们必须把我们所有与I/O相关的代码放到try/catch块中。接下来,我们必须创建一个FileOutputStream,以把对象写入其中。然后,我们在一个 ObjectOutputStream中包装FileOutputStream,ObjectOutputStream是带有我们需要的神奇序列化方法的类。记住:调用writeObject()会执行两个任务:它序列化对象,然后把经过序列化的对象写到文件。
(4)通过调用readObject()方法反序列化Cat对象。readObject()方法返回一个Object,因此,我们必须把饭序列化的对象强制转换回Cat。同样,我们必须经过典型的I/O循环来做到这一点。
这是实际中的序列化机制的主体部分。在后面,我们将介绍一些与序列化关联的更复杂的问题。
2、对象图
保存一个对象实际上意味着什么?如果实例变量全部是基本类型,那么这个问题非常简单。但是,如果实例变量自身指向对象的引用,又会如何呢?会保存什么内容?显然,在Java中,保存引用变量的实际值没有任何意义,因为Java引用的值只有JVM单一实例的环境中才有意义。换句话说,如果你尝试在JVM的另一个实例中恢复对象,即使运行在对对象进行最初序列化的同一台计算机上,引用也毫无用处。
但是,该引用所引用的对象会发生什么情况?观察下面这个类:
class Dog {
private Collar theCollar;
private int dogSize;
public Dog(Collar collar, int size) {
theCollar = collar;
dogSize = size;
}
public Collar getCollar() {
return theCollar;
}
}
class Collar {
private int collarSize;
public Collar(int size) {
collarSize = size;
}
public int getCollarSize() {
return collarSize;
}
}
现在建立一个Dog……首先,为Dog建立一个Collar:
Collar c = new Collar(3);
然后,建立一个新的Dog,并把Collar传递给它:
Dog d = new Dog(c, 8);
现在,如果保存Dog,会发生什么情况?如果目标是保存然后恢复Dog,那么恢复的Dog就是保存的Dog的完全一样的复制品,然后该Dog需要一个 Collar,它是保存Dog时Dog的Collar的完全一样的复制品。这意味着Dog和Collar都应该被保存。
如果Collar自身具有对其他对象的引用(例如,可能是一个Color对象),又会发生什么情况?这使问题极快地变得相当复杂。如果它要求程序员知道 Dog引用的每个对象的内部结构,使得程序员可以确信保存了所有这些对象的所有状态……即使对于最简单的对象,这也是一个梦魇。
幸运的是,Java序列化机制处理了所有这些工作。当你序列化一个对象时,Java序列化机制会保存对象的完整的“对象图”。这是恢复保存的对象所需一切内容的深复制。例如,如果序列化一个Dog对象,将自动序列化Collar。如果Collar类包含一个指向另一对象的引用,则那个对象也会被序列化,以此类推。你必须关心其保存和恢复的唯一对象就是Dog。还需要其他的对象,以通过序列化机制自动地完全重构被保存(和恢复)的Dog。
记住:你确实必须通过实现Serializable接口,有意识地选择创建可序列化的对象。例如,如果我们想保存Dog对象,我们将不得不按如下方式修改Dog类:
class Dog implements Serializable {
//the rest of the code as before
//Serializable has no methods to implement
}
现在,我们可以用下面的代码保存Dog:
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeDog {
public static void main(String[] args) {
Collar c = new Collar(3);
Dog d = new Dog(c, 8);
try {
FileOutputStream fs = new FileOutputStream("testSer.ser");
ObjectOutputStream os = new ObjectOutputStream(fs);
os.writeObject(d);
os.close();
}
catch(Exception e) {
e.printStackTrace();
}
}
}
但是,当我们在运行这段代码时,将会得到一个运行时异常,如下:
java.io.NotSerializableException:Collar
我们忘了什么?Collar类也必须是可序列化的。如果我们修改Collar类,并使之可序列化,那么就不会再有任何问题:
class Collar implements Serializable {
private int collarSize;
public Collar(int size) {
collarSize = size;
}
public int getCollarSize() {
return collarSize;
}
}
完整的程序清单如下:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializeDog {
public static void main(String[] args) {
Collar c = new Collar(3);
Dog d = new Dog(c, 8);
System.out.println("before: collar size is "
+ d.getCollar().getCollarSize());
try {
FileOutputStream fs = new FileOutputStream("testSer.ser");
ObjectOutputStream os = new ObjectOutputStream(fs);
os.writeObject(d);
os.close();
}
catch(Exception e) {
e.printStackTrace();
}
try {
FileInputStream fis = new FileInputStream("testSer.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
d = (Dog)ois.readObject();
ois.close();
}
catch(Exception e) {
e.printStackTrace();
}
System.out.println("after: collar size is "
+ d.getCollar().getCollarSize());
}
}
class Dog implements Serializable {
private Collar theCollar;
private int dogSize;
public Dog(Collar collar, int size) {
theCollar = collar;
dogSize = size;
}
public Collar getCollar() {
return theCollar;
}
}
class Collar implements Serializable {
private int collarSize;
public Collar(int size) {
collarSize = size;
}
public int getCollarSize() {
return collarSize;
}
}
其输出如下:
before: collar size is 3
after: collar size is 3
但是,如果我们不能访问Collar类的源代码,则会如何?换句话说,如果使得Collar类可序列化不是一个选项,则会如何?我们会坚持一个不可序列化的Dog吗?
显然,如果我们可以对Collar类再分子类,把孩子标识为Serializable,然后使用Collar子类而不是Collar类。但是,由于 以下几个潜在的原因,并非总能选择使用这样的方法:
(1)Collar类可能是最终类,从而无法再分子类;
(2)Collar类自身可能引用其他不可序列化的对象,并且如果不知道Collar的内部结构,就不能执行所有这些修正(假定你甚至尝试采用这种方法);
(3)由于和你的设计相关的其他原因,再分子类不可选。
那么……如果你想保存一个Dog,接下来要做什么?
这时,transient修饰符就闪亮登场了。如果把Dog的Collar实例变量标识为transient,那么在序列化期间,序列化将会简单地跳过Collar:
class Dog implements Serializable {
private transient Collar theCollar; //Add transient
private int dogSize;
public Dog(Collar collar, int size) {
theCollar = collar;
dogSize = size;
}
public Collar getCollar() {
return theCollar;
}
}
class Collar {
private int collarSize;
public Collar(int size) {
collarSize = size;
}
public int getCollarSize() {
return collarSize;
}
}
现在,我们具有一个可序列化的Dog,以及一个不可序列化的Collar,但是,Dog把Collar标识为transient。输出如下:
before: collar size is 3
Exception in thread "main" java.lang.NullPointerException
那么,现在我们要做什么

浙公网安备 33010602011771号