java的对象序列化将那些实现了Serializable接口的对象装换成一个字节序列,并能够在以后将这个字节序列完全恢复成原来的对象。这意味着序列化机制能自动弥补不同操作系统之间的差异
public interface Serializable {}
可以看到该接口仅是一个标记接口,不包括任何方法。
使用对象流实现序列化
创建一个可序列化的bean,Data类
public class Data implements Serializable{
private int id;
private String name;
public Data(int id, String name) {
System.out.println("Data()");
this.id = id;
this.name = name;
}
@Override
public String toString() {
return id + " : " + name;
}
}
对它进行存储和读取
public class Test1 {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException{
Data data = new Data(0, "test");
System.out.println("before: " + data);
System.out.println("output:");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test.txt"));
out.writeObject(data);
out.close();
System.out.println("input: ");
ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
Data data****2 = (Data) in.readObject();
System.out.println("after: " + data2);
}
}
可以看到结果:
Data()
before: 0 : test
output:
input:
after: 0 : test
对Serializable对象进行还原的过程中,没有调用任何构造器,包括默认的构造器,整个对象都是由Inputstream中取得数据恢复过来的。
反序列化恢复对象时,必须保证Java虚拟机能够找到相关的.class文件,否则会引发ClassNotFoundException异常;
序列化的控制
如果不希望对象的某一部分被序列化时,可以对序列化进行控制
- 使用Externalizable接口
- transient关键字
- 实现Serializable接口,并添加名为writeObject(), readObject()方法。
1、使用Externalizable接口
Externalizable接口继承自Serializable接口,并添加了writeExternal()和readExternal()方法
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
在默认的情况不保存任何字段
public class Dog implements Externalizable{
private int age;
private String name;
public Dog() {
System.out.println("Dog()");
}
public Dog(int age, String name){
System.out.println("Dog(age, name)");
this.age = age;
this.name = name;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
@Override
public String toString() {
return "name: " + name + " age: " + age;
}
}
public class Test2 {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
Dog dog = new Dog(0, "test");
System.out.println("before: " + dog);
System.out.println("output:");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test.txt"));
out.writeObject(dog);
out.close();
System.out.println("input: ");
ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
Dog dog2 = (Dog) in.readObject();
System.out.println("after: " + dog2);
}
}
结果:
Dog(age, name)
before: name: test age: 0
output:
input:
Dog()
after: name: test age: 0
可以注意到的是,恢复的时候,会调用默认的构造器,对于Serializable对象而言,对象完全以它存储的二进制位为基础来构造,而不调用构造器,但是对于Externalizable,所有普通的默认构造器都会被调用。
2、transient(瞬时)关键字
如果我们正在操作的是一个Serializable对象,所有的序列化操作都会自动进行,为了控制,可以奖赏transient关键字,会逐个字段地关闭序列化。
将第一个例子中的name加上transient关键字
private transient String name;
结果变成
Data()
before: 0 : test
output:
input:
after: 0 : null
id域是一般的,会自动序列化,但是name添加了transient关键字,不会进行存储,自动序列化机制也不会尝试去恢复它,所以变成了null
Externalizable对象在默认的情况下是不会保存它的任何字段,所以transient字段只能和Serializable一起用
3、Externalizable的替代方法
可以实现Serializable接口,并添加名为writeObject()和readObject()的方法,一旦对象被序列化或者被反序列化,就会自动的调用这两个方法,而不是使用默认的序列化机制
public class Cat implements Serializable{
public int age;
public transient String name;
public static String home = "catHome";
public Cat() {
System.out.println("Cat()");
}
@Override
public String toString() {
return "name(transient): " + name + "\nage(not transient): " + age + "\nhome(static): " + home;
}
public Cat(int age, String name, String home){
System.out.println("Cat(age, name, home)");
this.age = age;
this.name = name;
this.home = home;
}
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
stream.writeObject(name);//明确保存和恢复
}
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
name = (String) stream.readObject();
}
}
ObjectOutputStream 和ObjectInputStream对象的writeObject()和readObejct()方法调用对象的writeObject()和readObject()方法
这两个方法是private的,那么它们不会是接口的一部分,但其效果就和实现了接口一样。
writeObject()会检查,判断是否拥有自己的writeObject()方法(利用反射),有就使用它。
非transient字段由defaultWriteObject()方法保存(作为第一个操作),transient字段必须在程序中明确保存和恢复。
static值不被序列化
尝试这样的代码
public class Test2 {
public static void main(String[] args) throws FileNotFoundException, ClassNotFoundException, IOException{
Cat cat = new Cat(0, "test", "here");
System.out.println("before: " + cat);
System.out.println("output:");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test.txt"));
out.writeObject(cat);
out.close();
System.out.println("input: ");
ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
Cat cat2 = (Cat) in.readObject();
System.out.println("after: " + cat2);
}
}
输出:
Cat(age, name, home)
before: name(transient): test
age(not transient): 0
home(static): here
output:
input:
after: name(transient): test
age(not transient): 0
home(static): here
static变量并没有被序列化,所以它并没有被写入流中,读取值时,它不可能在反序列化的文件里找到新的值,而是去全局数据区取值,因为全局数据区的值现在是here,所以读取出来的值就是改变后的值here了。
在另外一个InputStream
public class Test3 {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
System.out.println("input: ");
ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
Cat cat2 = (Cat) in.readObject();
System.out.println("after: " + cat2);
}
}
输出:
input:
after: name(transient): test
age(not transient): 0
home(static): catHome
这时输出的为catHome。因为没有序列化static值,所以上面的here值并没有被写入流中。在另外一个线程读取的时候,它读取到的是全局数据域catHome
深度复制
- 浅拷贝(Shallow Copy影子克隆):只复制对象的基本类型,对象类型,仍属于原来的引用。
- 深拷贝(Deep Copy 深度克隆):不仅复制对象的基本类,同时也复制原对象中的对象.完全产生新对象。
Java序列化采用了特殊的序列化算法
- 所有保存到磁盘中的对象都有一个序列化编号;
- 当程序视图序列化一个对象时,程序将先检查该对象是否已经被序列化过,只有该对象从未(在本次虚拟机)中序列化过,系统才会将该对象转换成字节序列并输出;
- 如果某个对象已经序列化过,程序将只是直接输出一个序列化编号,而不是再次重新序列化该对象;
因为这种序列化机制,如果多次序列化同一个对象时,只有第一次序列化时才会把该对象转换成字节流并输出,即使后面对象的域已经改变
将Dog类的toString()改为
@Override
public String toString() {
return super.toString();
}
public class Test4 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Dog dog = new Dog(1, "candy");
System.out.println(dog);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test.txt"));
out.writeObject(dog);
out.writeObject(dog);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
Dog dog2 = (Dog) in.readObject();
System.out.println("after: " + dog2);
Dog dog3 = (Dog) in.readObject();
System.out.println("same stream: " + dog3);
}
}
输出:
com.example.serializable.Dog@15db9742
after: com.example.serializable.Dog@65ab7765
same stream: com.example.serializable.Dog@65ab7765
序列化前后对象的地址不同了,但是内容是一样的,而且对象中包含的引用也相同。通过序列化操作,我们可以实现对任何Serializable对象的”深度复制(deep copy)”——这意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。
对于同一流的对象,他们的地址是相同,说明他们是同一个对象,但是与其他流的对象地址却不相同。也就说,只要将对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,而且只要在同一流中,对象都是同一个。
参考:《Thinking in java》
Java 序列化Serializable详解(附详细例子)
Java:对象的序列化