Java 序列化

综述

概念:

所谓序列化,是指Java可以将任何对象写出到流中,并且在任何时候将其读回,这样我们就可以方便的将Java的对象进行保存、回复。

一:Serializable接口

java.io.Serializable接口,该接口是最为常用并且最为简单使用的序列化接口,只要我们在想要序列化的类上继承该接口,就可以自动实现该类的序列化。

1、使用:

参考下面的代码:

package Serializable;

import java.io.Serializable;

public class Fruit implements Serializable{
	public String name;
	public float price;

	public Fruit(String name,float price){
		this.name = name;
		this.price = price;
	}
	public void show(){};
}
package Serializable;

import java.io.Serializable;

public class Apple extends Fruit implements Serializable{

	public Apple(String name,float price){
		super(name, price);
	}

	@Override
	public void show(){
		System.out.println("A apple named:"+name+" price:"+price);
	}
}
package Serializable;

import java.io.Serializable;

public class Orange extends Fruit implements Serializable{

	public Orange(String name,float price){
		super(name, price);
	}

	@Override
	public void show(){
		System.out.println("A orange named:"+name+" price:"+price);
	}
}
package Serializable;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializableInterfaceTest {

	private static final long serialVersionUID = 8449604890628424672L;

	public static void main(String[] args){
		try {
			ObjectOutputStream op = new ObjectOutputStream(new FileOutputStream(new File("testSerializable")));
			op.writeObject(new Apple("apple", 1.1f));
			op.writeObject(new Orange("orange",2.2f));
			op.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			System.out.println("File not found!");
		} catch (IOException e) {
			// TODO: handle exception
			e.printStackTrace();
		}

		try {
			ObjectInputStream oi = new ObjectInputStream(new FileInputStream(new File("testSerializable")));
			Apple apple = (Apple)oi.readObject();
			Orange orange = (Orange)oi.readObject();
			apple.show();
			orange.show();

		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			System.out.println("File not found!");
		} catch (IOException e) {
			// TODO: handle exception
			e.printStackTrace();
		} catch(ClassNotFoundException e){
			e.printStackTrace();
		}
	}
}

程序运行结果:

A apple named:apple price:1.1
A orange named:orange price:2.2

生成的testSerializable文件内容为:

��srSerializable.AppleE-�f����xrSerializable.Fruit_,�}�&��FpriceLnametLjava/lang/String;xp?���tapplesrSerializable.Orange���0h�xq~@

可以看到,通过 java.io.Serializable接口,可以实现类的序列化,并将序列化之后的数据写入文件,并重新从文件读出。

2、注意事项:

仔细观察上面的程序,Fruit基类、Apple子类、Orange子类都实现了序列化,那么问题来了,如果说父类和子类有没有实现该接口的情况下会怎么样呢?

将Fruit基类去掉对Serializable的接口,重新运行该程序,会发现如下的错误:

java.io.InvalidClassException: Serializable.Apple; no valid constructor
	at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
	at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1770)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1348)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
	at Serializable.SerializableInterfaceTest.main(SerializableInterfaceTest.java:32)

会发现在程序写入序列化的时候,没有任何错误的提示,但在读入数据的时候,却提示错误,我们看写入的文件内容有什么变化

��srSerializable.AppleE-�f����xpsrSerializable.Orange���0h�xp

会发现确实只序列化了Apple和Orange子类,但重新构建的时候,是找不到Fruit基类的,也就是上面,没有相应的构造函数。

原因是什么?

我们继续探究,如果Fruit基类继承了该接口,但子类Apple没有继承该接口呢?重新编译、运行,发现能够正常运行,那是不是如果父类继承了该接口,子类就可以自动的序列化呢?从Java继承的角度来将,是这样理解的,父类继承了该接口,子类当然可以,但Java的Serializable接口是使用了Java的Reflection机制实现的,因此我们继续探究一下,当前的Apple类过于简单,我们再稍微加一点内容,将Apple修改如下:

package Serializable;

import java.io.Serializable;

public class Apple extends Fruit{

	public int productYear;

	public Apple(String name,float price){
		super(name, price);
		productYear = 2013;
	}

	@Override
	public void show(){
		System.out.println("A apple named:"+name+" price:"+price +" product:"+productYear);
	}
}

在这里增加了一个int的productYear属性,执行结果为:

A apple named:apple price:1.1 product:2013
A orange named:orange price:2.2

所以可以看出来,如果父类继承了Serializable接口的话,子类也可以自动继承该接口,并实现序列化。

那如果父类没有继承该接口,我们写子类,想要实现Serializable接口,怎么去做呢?http://docs.oracle.com/javase/7/docs/api/ API Reference中有这样一段:

During deserialization, the fields of non-serializable classes will be initialized using the public or protected no-arg constructor of the class. A no-arg constructor must be accessible to the subclass that is serializable. The fields of serializable subclasses will be restored from the stream.

 如果说,子类要实现序列化,那么要求父类实现序列化,或者父类拥有无参构造函数。

父类实现序列化的方法已经验证过了,那么验证一下父类拥有无参构造函数的情况,修改Furit代码为:

package Serializable;

public class Fruit{
	public String name;
	public float price;

	public Fruit(String name,float price){
		this.name = name;
		this.price = price;
	}

	public Fruit(){
		this.name = "Default";
		this.price = 0.1f;
	}
	public void show(){};
}

同时,为Apple子类和Orange子类添加无参构造函数,运行结果为下:

A apple named:Default price:0.1
A orange named:Default price:0.1

在反序列化的时候,如果父类没有实现序列化,那么JVM调用了父类的无参构造函数,为子类中的父类对象进行了实例化。

3、修改默认的序列化机制:

1)有数据不希望被序列化?

使用默认的序列化策略,类中所有的数据域都会被序列化。但在实际使用的时候,很多的情况下我们是不希望数据被序列化的,这时候怎么办?在Java中有一个很简单的机制可以实现该目的,使用transient关键字。e.g.

我们将上面程序中的基类Fruitjava进行修改,改动如下:

改动之前

public String name;

改动之后

public transient String name;

其实改动很少,只是将 name 加上了 transient关键字,再次运行程序,结果如下:

A apple named:null price:1.1
A orange named:null price:2.2

达到目的,所有的名称都没有从文件中的反序列化中得到。默认值为null

2)自定义序列化的对象

如果希望完全在Serializable接口基础上实现自定义序列化,需要自己实现witeObject接口和readObject接口,同时,在调用writeObject接口的时候,要先调defaultWriteObject()来使得自定义的序列化仍然使用Java Serializable默认的机制,当然,在读取的时候,要先调用 defaultReadObject(),也是一样的道理。

e.g 我们可以对Fruit基类做如下的修改:

	private void writeObject(ObjectOutputStream out) throws IOException{
		out.defaultWriteObject();
		out.writeObject(name);
		out.writeFloat(price);
	}

	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
		in.defaultReadObject();
		this.name = (String)in.readObject();
		this.price = in.readFloat();
	}

4、一个对象被多个对象共享的时候:

比如将代码按下列修改

1)添加类Owner

package Serializable;
import java.io.Serializable;
public class Owner implements Serializable{
	public String name;
	public Owner(String name){
		this.name = name;
	}
}

2)修改Fruit基类,增加设置Owner属性

package Serializable;

import java.io.Serializable;

public class Fruit implements Serializable{
	public String name;
	public float price;
	public Owner owner;

	public Fruit(String name,float price){
		this.name = name;
		this.price = price;
	}

	public void setOwner(Owner owner){
		this.owner = owner;
	}

	public Fruit(){
		this.name = "Default";
		this.price = 0.1f;
	}
	public void show(){};
}

3)修改测试程序,为每个Fruit设置Owner

			ObjectOutputStream op = new ObjectOutputStream(new FileOutputStream(new File("testSerializable")));
			Apple apple = new Apple("apple", 1.1f);
			Orange orange = new Orange("orange",2.2f);
			Owner owner = new Owner("Happy");
			apple.setOwner(owner);
			orange.setOwner(owner);
			op.writeObject(apple);
			op.writeObject(orange);
			op.close();

程序运行结果不变,那我们观察生成的文件

��srSerializable.AppleD�[��^xrSerializable.Fruit"=�l
                                                          KFpriceLnametLjava/lang/String;LownertLSerializable/Owner;xp?���tapplesrSerializable.Ownerl[B0�Lnameq~xptHappysrSerializable.Orange�[ɪ�pd�xq~@
                                                      ��torangeq~

可以发现,在内容中,Owner只是提到一次,也就是因为两个Fruit的指向了同一个Owner,因此在序列化保存中,只是将该对象保存了一次。

在Java核心技术中是这样解释这一部分内容的:

每个对象用是用一个序列号(serial number)保存的,在写入过程其算法为:

1、对遇到的每一个对象引用都关联了一个序列号

2、对每一个对象,当第一次遇到的时候,都保存其对象数据到流中。

3、如果某个对象在之前被保存过,那么只写出“与之前保存过的序列号为x的对象相同”

在读取的过程中,其算法为:

1、对于流程的对象,在第一次遇到其序列号的时候,构建它,并使用流中的数据来初始化它,然后记录这个顺序号和新对象之间的关联。

2、当遇到“与之前保存过的序列号为x的对象相同”标记时候,获取与这个顺序号想关联的对象引用。

Tips:因为序列化用序列号代替了内存地址,所有允许将对象集合从一台机器传送到另外一台机器。

5、版本管理:

6、clone+serializable

书签Java

二:Externable

1、使用:

2、与Serializable区别

三:Android序列化Parcelable

四:开源序列化接口:

About: happyhls


发表评论

电子邮件地址不会被公开。 必填项已用*标注