1. 什么是Serialization?
串行化(Serialization)是计算机科学中的一个概念,它是指将对象存储到介质(如文件、内在缓冲区等)中或是以二进制方式通过网络传输。之后可以通过反串行化从这些连续的位数据重新构建一个与原始对象状态相同的对象,因此在特定情况下也可以说是得到一个副本,但并不是所有情况都这样。
Java有Serialization API为开发者提供了一种标准的机制来串行化类。
2. 为什么要Serilzation?
特别地,串行化主要有三种用途:
1)作为一种持久化机制
如果使用的是FileOutputStream流的方式,则数据将被自动地写入文件中,
2)作为一种复制机制
如果使用的是ByteArrayOutputStream
流的方式,数据将写入内存中的字节数组中。该字节数组可以用来创建初始对象的副本,
3)作为一种通信机制
如果是使用套接字(Socket)流的方式,则数据自动地通过网络连接传输一另一个端点,并由这个端点上的程序来决定做什么。
3. Serialization的基本用法:默认机制
将要串行化的类必须实现java.io.Serializable接口,或者是继承实现了该接口的类。然后通过java.io.ObjectOutputStream类来实现持久化,如果用保存到文件上还需要用到java.io.FileOutputStream类。因为ObjectOutputStream被认为是java.io包中的高级类所以可用它来包装低级的类FileOutputStream。在持久化过程中调用的一个方法是ObjectOutputStream对象的writeObject(obj)方法。
当要从文件中恢复对象时,则是使用java.io.OjbectInputStream与FileInputStream类,调用一方法是ObjectInputStream对象的readObject()方法。
示例1:
import java.io.*;
public class Cat implements Serializable {
private String name;
public Cat () {
this.name = "new cat";
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
import java.io.*;
public class CatDemo {
public static void main(String[] args) {
Cat cat = new Cat();
try { //串行化
FileOutputStream fos = new FileOutputStream("catDemo.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
System.out.println(" 1> " + cat.getName());
cat.setName("My Cat");
oos.writeObject(cat);
oos.close();
} catch (IOException ex) {
ex.printStackTrace();
}
try { //反串行化
FileInputStream fis = new FileInputStream("catDemo.out");
ObjectInputStream ois = new ObjectInputStream(fis);
cat = (Cat) ois.readObject();
System.out.println(" 2> " + cat.getName());
ois.close();
} catch (IOException ex) {
ex.printStackTrace();
}catch(ClassNotFoundException ex) {
ex.printStackTrace();
}
}
}
4. Serialization常见问题
正如前面提到的,所有可串行化的类必须直接或是通过继承方式间接地实现java.io.Serializable接口,由于Object类关没有实现这个接口,所以并不是所有类的对象都是可串行化的。像AWT与Swing的GUI组件、字符串、数组等都是可串行化的,而像一些系统级的类(Thread,OutputStream
等)和Socket类是不可串行化的。
问题一:如果在一个可串行化的类中Has-As不可串行化的类该怎么处理?
在这种情况下在运行时会抛出NotSerializableException。
为了解决类似问题,Java中提供了transient关键字来跳过对不可串行化类的对象的处理。但这依然可能会引起一些问题,在反串行化时,被标识为transient变量不会恢复到其原始状态,而是提供默认值,如示例2中的pig引用将赋值为null,age变量赋值为0;
附:基本类型和引用类型的默认值
对象引用:null
byte, short, int, long :0
float, double:0.0
boolean:false
char:'\u0000'(这是Unicode字符集的空格)
示例2:
import java.io.*;
public class NewPig2 implements Serializable {
private String newName;
private transient Pig pig = new Pig();
private transient int age = 2;
public NewPig2() {
newName = "new Pig 2";
//pig = new Pig();
}
public Pig getPig() {
return this.pig;
}
public void setName(String name) {
this.newName = name;
}
public String getName() {
return this.newName;
}
public int getAge() {
return this.age;
}
}
问题二:如果父类不可串行化,子类实现了Serializable会怎样?
如果有一个Animal类是不可串行化的,而有一个Dog类继承自Animal类并且实现了Serializabl接口,则没有串行化时没有任何问题,但是在反串行化时将会重新调用Animal的构造函数,如示例3所示。
示例3的运行结果如下:
1> No Color - new Dog
2> Green - My Dog
4> No Color - My Dog
因为Animal不可串行化,所以必须运行构造函数,但不会在实现Serializable的反串行化类上运行构造函数。
示例3:
public class Animal {
private String color;
public Animal () {
this.color = "No Color";
}
public void setColor(String color) {
this.color = color;
}
public String getColor () {
return this.color;
}
}
import java.io.*;
public class Dog extends Animal implements Serializable {
private String name;
public Dog () {
this.name = "new Dog";
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
import java.io.*;
public class DogTest {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(" 1> " + dog.getColor() + " - " + dog.getName());
dog.setColor("Green");
dog.setName("My Dog");
System.out.println(" 2> " + dog.getColor() + " - " + dog.getName());
try {//串行化
FileOutputStream fos = new FileOutputStream("myDog.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(dog);
oos.close();
} catch (Exception ex) {
ex.printStackTrace();
}
try {//反串行化
FileInputStream fis = new FileInputStream("myDog.out");
ObjectInputStream ois = new ObjectInputStream(fis);
dog = (Dog) ois.readObject();
System.out.println(" 4> " + dog.getColor() + " - " + dog.getName());
ois.close();
} catch (Exception ex) {
ex.printStackTrace();