概述:当一个类实现了Serializable接口(该接口仅为标记接口,不包含任何方法定义),表示该类可以序列化.序列化的目的是将一个实现了Serializable接口的对象转换成一个字节序列,可以。
把该字节序列保存起来(例如:保存在一个文件里),以后可以随时将该字节序列恢复为原来的对象。甚至可以将该字节序列放到其他计算机上或者通过网络传输到其他计算机上恢复,只要该计
算机平台存在相应的类就可以正常恢复为原来的对象。
实现:要序列化一个对象,先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内,再调用writeObject()方法即可序列化一个对象;反序列化也类似。
import java.io.*;
public class Person implements Serializable {
private String userName;
private String password;
public Person(String userName, String password) {
this.userName = userName;
this.password = password;
}
public String toString() {
return "userName:" + userName + " password:" + password;
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
//序列化一个对象(存储到一个文件)
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.out"));
oos.writeObject("Save a object:\n");
oos.writeObject(new Person("Bruce", "123456"));
oos.close();
//反序列化,将该对象恢复(存储到一个文件)
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.out"));
String s = (String)ois.readObject();
Person p = (Person)ois.readObject();
System.out.println(s + p);
//序列化一个对象(存储到字节数组)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos2 = new ObjectOutputStream(baos);
oos2.writeObject("Save another object:\n");
oos2.writeObject(new Person("Phil", "654321"));
oos2.close();
//反序列化,将该对象恢复(存储到字节数组)
ObjectInputStream ois2 = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
s = (String)ois2.readObject();
p = (Person)ois2.readObject();
System.out.println(s + p);
}
}
//输入如下信息
Save a object:
userName:Bruce password:123456
Save another object:
userName:Phil password:654321
transient关键字:
自动序列化将对象的所有字段都持久化了,有时候需要对某些字段不进行自动化(如密码,因为序列化会暴光密码信息),这个时候可以使用transient关键字(只能和Serializable对象一起使
用),其作用是不序列化某些字段。将Person类的字段改为如下定义,再运行上面的程序:
private String userName;
private transient String password;
//输入如下信息
Save a object:
userName:Bruce password:null
Save another object:
userName:Phil password:null
控制序列化字段,甚至该字段是被transient修饰的字段也能将其序列化。手动序列化需要添加两个私有(private)方法(writeObject()和readObject()),在该私有方法中控制序列花字段。
import java.io.*;
public class Person implements Serializable {
private String userName;
private transient String password;
public Person(String userName, String password) {
this.userName = userName;
this.password = password;
}
public String toString() {
return "userName:" + userName + " password:" + password;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); //序列化所有非transient字段,必须是该方法的第一个操作
out.writeObject(password); //序列化transient字段
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); //反序列化所有非transient字段,必须是该方法的第一个操作
password = (String)in.readObject(); //反序列化transient字段
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
//序列化一个对象(存储到一个文件)
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.out"));
oos.writeObject("Save a object:\n");
oos.writeObject(new Person("Bruce", "123456"));
oos.close();
//反序列化,将该对象恢复(存储到一个文件)
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.out"));
String s = (String)ois.readObject();
Person p = (Person)ois.readObject();
System.out.println(s + p);
}
}
//输入如下信息
Save a object:
userName:David password:13579
控制序列化字段还可以使用Externalizable接口替代Serializable借口。此时需要定义一个默认构造器,否则将为得到一个异常(java.io.InvalidClassException: Person; Person; no
valid constructor);还需要定义两个方法(writeExternal()和readExternal())来控制要序列化的字段。
import java.io.*;
public class Person implements Externalizable {
private String userName;
private String password;
public Person() {
System.out.println("default constructor invoked!");
}
public Person(String userName, String password) {
this.userName = userName;
this.password = password;
}
public String toString() {
return "userName:" + userName + " password:" + password;
}
public void writeExternal(ObjectOutput out) throws IOException {
//序列化字段
out.writeObject(userName);
out.writeObject(password);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
//反序列化字段
userName = (String)in.readObject();
password = (String)in.readObject();
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
//序列化一个对象(存储到一个文件)
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.out"));
oos.writeObject("Save a object:\n");
oos.writeObject(new Person("Leo", "1984"));
oos.close();
//反序列化,将该对象恢复(存储到一个文件)
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.out"));
String s = (String)ois.readObject();
Person p = (Person)ois.readObject();
System.out.println(s + p);
}
}
//输入如下信息
default constructor invoked!
Save a object:
userName:Leo password:1984
serialVersionUID是long类型的。在Eclipse中有两种生成方式:
默认的是1L:
private static final long serialVersionUID = 1L;
另外一个则是根据类名、接口名、成员方法以及属性等生成一个64位的哈希字段:
private static final long serialVersionUID = 3969438177161438988L;
如果没有提供serialVersionUID,对象序列化后存到硬盘上之后,再增加或减少类的filed。这样,当反序列化时,就会出现Exception,造成不兼容问题。
但当serialVersionUID相同时,它就会将不一样的field以type的缺省值反序列化。这样就可以避开不兼容问题了。
以上方式只能恢复成Java对象,如果想要恢复成其他对象(如C++对象),那就要将Java对象转换为XML格式,这样可以使其被各种平台和各种语言使用。可以使用随JDK一起发布的javax.xam.*类库,或者使用开源XOM类库(可以从www.xom.nu下载并获得文档)。
使用java以来,序列化随处可见,至于为什么要用序列化、序列化能解决什么问题,作为一个普通的码农,一般不怎么会去深入研究,由于最近在看mina和公司内部涉及到nio框架的一些源码,里面涉及到hession、java这两种序列化,至于hession序列化为什么会诞生以及在apache项目中使用如此广泛,以及java本身序列化存在哪些缺陷,甚是不解,为了解答上面抛出来的疑惑,以及进一步了解java的序列化机制,这里开个小头,从java的序列化接口Serializable开始说起
jdk包里的Serializable接口的注释主要说明了以下几点:
1.类通过实现Serializable接口来启用序列化,否则该类的任何状态将无法被序列化,同时也无法用于反序列化
2.若继承的父类没有实现Serializable接口,但是又想让子类可序列化,有三个注意事项:
a)。子类实现Serializable接口
b)。子类必须有可访问的无参构造方法,用于保存和恢复父类的public或protected或同包下的package字段的状态,否则在序列化或反序列化时会抛出RuntimeException异常
c)。对于序列化后的子类,在进行反序列化时,理论上无法初始化父类中private(不可访问)对象变量的状态或值
3.在对可序列化类中的属性进行序列化时,如果遇到不可序列化的对象变量,此时会针对不可序列化的类抛出NotSerializableException异常
4.对于可序列化的非数组类,强烈建议显示声明static型、long型、final型serialVersionUID字段用于标识当前序列化类的版本号,否则在跨操作系统、跨编译器之间进行序列化和反序列化时容易出现InvalidClassException异常
5.对于可序列化类中的static、transient对象变量,在序列化时无法保存其状态或值,static对象变量在反序列化时取得的值为当前jvm中对应类中对应static变量的值,而transient(瞬态)关键字则一般用于标识那些在序列化时不需要传递的状态变量
简单的测试代码:
01 import java.io.FileInputStream;02 import java.io.FileNotFoundException;03 import java.io.FileOutputStream;04 import java.io.IOException;05 import java.io.ObjectInputStream;06 import java.io.ObjectOutputStream;07 import java.io.Serializable;08
09 /** 10 * 序列化测试11 * 12 * @author sume 13 * 14 */ 15 public class SerializableImpl implements Serializable { 16
17 private static final long serialVersionUID = -6433786313435044319L;18
19 static String staticVal = "static1";20 transient String transientVal = "transient1";21 String val = "val1";22
23 /** 24 * main 25 */ 26 public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { 27 // 序列化28 SerializableImpl sila1 = new SerializableImpl();29 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("Serializable.txt"));30 objectOutputStream.writeObject(sila1);31 objectOutputStream.close();32
33 // 反序列化34 SerializableImpl.staticVal = "static2";35 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("Serializable.txt"));36 SerializableImpl sila2 = (SerializableImpl) objectInputStream.readObject();37 objectInputStream.close();38
39 // 比较各个属性的值40 System.out.println(sila2.staticVal);41 System.out.println(sila2.transientVal);42 System.out.println(sila2.val);43 } 44 }输出结果:1 static2 2 null 3 val1从输出结果可以看出:
1.反序列化后类中static型变量staticVal的值为当前jvm中对应static变量的值,为:static2,而不是序列化时的值:static1
2.transient关键字标识的变量的状态并没有在序列化中被保存,因此反序列化后
transientVal变量的值为null
3.第三个为常见的对象状态在序列化和反序列化过程中的传递
简单印证了前面所说的几点内容