java对象的序列化

序列化就是将对象的状态信息转换为可以存储或者传输的形式的过程。在JVM运行时,java对象存在于内存中。但当JVM生命周期结束,它将不复存在。但在实际应用中,会用到在JVM停止后保存制定的对象。除此之外,在使用RMI(远程方法调用)或者在网络中传递对象时,对需要对对象进行序列化。
1.创建可以序列化的类:
让一个类可以序列化很简单,只要实现java.io.Serializable接口便可以了。该接口中没有定义任何方法,它的作用只是告诉大家该类是可以序列化的。下面来编写这样一个类:

public class Person implements Serializable {
	private String name;
	private Gender gender;
	public Person(String name, Gender gender) {
		this.name = name;
		this.gender = gender;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Gender getGender() {
		return gender;
	}
	public void setGender(Gender gender) {
		this.gender = gender;
	}
}

该类的属性里面有一个枚举类型

public enum Gender {
	 MALE, FEMALE
}

因为枚举类型是默认实现java.io.Serializable接口的,所以可以作为该类的属性值。
现在lz要持久化这个对象:

public static void main(String[] args) throws Exception {
	File file = new File("person.out");
	ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
	Person person = new Person("ee", Gender.MALE);
	oout.writeObject(person);
	oout.close();
}

运行会生成一个person.out文件。lz将它放入另一个项目中去读取,看能不能将该对象还原:

public static void main(String[] args) throws Exception {
	File file = new File("person.out");
	ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
	Object newPerson = oin.readObject(); // 没有强制转换到Person类型
	oin.close();
	System.out.println(newPerson);
}

结果运行时报错了java.lang.ClassNotFoundException。原来对象序列化只是保存对象的状态,即它的成员变量,对象是不会被保存的。所以还原时必须确保读取程序的CLASSPATH里必须包含包名一致的Person.class,并且该Person.class与序列化程序中的Person.class的serialVersionUID必须是相同的,这样才可以将该对象还原。

而且,因为是保存对象的状态,也就是成员变量,所以在序列化时不会关注类中的静态变量。

2.序列化中忽略某些字段:

lz在Person类中在添加一个字段

public class Person implements Serializable {
	...
	private Group group;
	public Group getGroup() {
		return group;
	}
	public void setGroup(Group group) {
		this.group = group;
	}
	...
}

该类定义为

public class Group {
	private String name;
	public Group(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

lz没有让Group类实现java.io.Serializable接口,现在再序列化Person类,结果抛出了异常java.io.NotSerializableException,Group类不能进行序列化。java序列化默认,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化程。transient关键字可以做到这点。

当某个字段被声明为transient后,默认序列化机制就会忽略该字段。lz将Person类中的name字段声明为transient

public class Person implements Serializable {
	...
	private transient String name;
	...
}

还原后输出为Person [name=null, gender=MALE, group=Group [name=baidu]],name字段未被序列化。

3.Serializable接口的作用:

通过ObjectOutputStream类的源码,来看看Serializable到底起了什么作用


private void writeObject0(Object obj, boolean unshared) throws IOException {
    ...
    if (obj instanceof String) {
        writeString((String) obj, unshared);
    } else if (cl.isArray()) {
        writeArray(obj, desc, unshared);
    } else if (obj instanceof Enum) {
        writeEnum((Enum) obj, desc, unshared);
    } else if (obj instanceof Serializable) {
        writeOrdinaryObject(obj, desc, unshared);
    } else {
        if (extendedDebugInfo) {
            throw new NotSerializableException(cl.getName() + "\n"
                    + debugInfoStack.toString());
        } else {
            throw new NotSerializableException(cl.getName());
        }
    }
    ...
} 

被写对象的类型是String,或数组,或Enum,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。

4.Externalizable接口:

还可以通过实现Externalizable接口来进行序列化,但是序列化的细节需要由程序员去完成。


public class Person implements Externalizable {
	private String name;
	private Gender gender;
	private Group group;
	public Person () {
		System.out.println("none-arg constructor");
	}
	public Person(String name, Gender gender, Group group) {
		System.out.println("arg constructor");
		this.name = name;
		this.gender = gender;
		this.group = group;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Gender getGender() {
		return gender;
	}
	public void setGender(Gender gender) {
		this.gender = gender;
	}
	public Group getGroup() {
		return group;
	}
	public void setGroup(Group group) {
		this.group = group;
	}
	@Override
	public void readExternal(ObjectInput input) throws IOException,
			ClassNotFoundException {
		name = (String)input.readObject();
		group = (Group)input.readObject();
	}
	@Override
	public void writeExternal(ObjectOutput output) throws IOException {
		output.writeObject(name);
		output.writeObject(group);
	}
}

输出的结果为
arg constructor
none-arg constructor
Person [name=ee, gender=null, group=Group [name=baidu]]

序列化的细节,lz在writeExternal()与readExternal()方法中进行了实现,通过这两个方法完成了对Person类的序列化。值得注意的是,运行中调用了Person类的无参构造器。因为在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。

1 Reply to “java对象的序列化”

发表评论