Java对象的序列化与反序列化
我们知道Java中的对象都是存在于堆内存中的,而堆内存是可以被垃圾回收器不定期回收的。从对象被创建到被回收这一段时间就是Java对象的生命周期,也即Java对象只存活于这个时间段内。
对象被垃圾回收器回收意味着对象和对象中的成员变量所占的内存也就被回收,这意味着我们就再也得不到该对象的任何内容了,因为已经被销毁了嘛,当然我们可以再重新创建,但这时的对象的各种属性都又被重新初始化了。所以如果我们需要保存某对象的状态,然后再在未来的某段时间将该对象再恢复出来的话,则必须要在对象被销毁即被垃圾回收器回收之前保存对象的状态。要保存对象状态的话,我们可以使用文件、数据库,也可以使用序列化。其中,对象的序列号就是将对象的状态信息转换为可以存储或传输的形式的过程。
下面我们来看这个StudentBean类,要序列化某个类的对象的话,则该类必要实现Serializable接口,从Java API中我们发现该接口是个空接口,即该接口中没声明任何方法。
public class StudentBean implements Serializable{
private int
id;
private /*transient */String name;
private int
age;
public int getAge() {
return age;
}
public
void setAge(int age) {
this.age = age;
}
public int
getId() {
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
下面我们看一下如何来进行序列化,这其中主要涉及到Java的I/O方面的内容,主要用到两个类FileOutputStream和ObjectOutputStream,FileOutputStream用于将字节输出到文件,ObjectOutputStream通过调用writeObject方法将对象转换为可以写出到流的数据。所以整个流程是这样的:ObjectOutputStream将要序列化的对象转换为某种数据,然后通过FileOutputStream连接某磁盘文件,再对象转化的数据转化为字节数据再将其写出到磁盘文件。下面是具体代码:
/**
* 对象的序列化
*/
public void write(){
StudentBean
bean=new
StudentBean();
bean.setId(1);
bean.setName("张三");
bean.setAge(16);
StudentBean
bean1=new
StudentBean();
bean1.setId(2);
bean1.setName("李四");
bean1.setAge(18);
ArrayList
list=new
ArrayList();
list.add(bean);
list.add(bean1);
FileOutputStream
fos=null;
ObjectOutputStream oos=null;
try {
fos=new
FileOutputStream("objcet.dat");
oos=new
ObjectOutputStream(fos);
oos.writeObject(list);
} catch
(FileNotFoundException e) {
// TODO Auto-generated catch
block
e.printStackTrace();
} catch (IOException e) {
//
TODO Auto-generated catch
block
e.printStackTrace();
}finally{
try
{
fos.close();
} catch (IOException e) {
// TODO
Auto-generated catch
block
e.printStackTrace();
}
}
}
我们存储的目的主要是为了再恢复使用,下面我们来看下加上反序列化后的代码:
/**
* 对象的反序列化
*/
public void
read(){
FileInputStream fis=null;
ObjectInputStream
ois=null;
try {
fis=new
FileInputStream("objcet.dat");
ois=new
ObjectInputStream(fis);
ArrayList<StudentBean> obj=(ArrayList)
ois.readObject();
for(int
i=0;i<obj.size();i++){
System.out.println("学号:"+obj.get(i).getId()+"
姓名:"+obj.get(i).getName()+" 年龄:"+obj.get(i).getAge());
}
}
catch (FileNotFoundException e) {
// TODO Auto-generated catch
block
e.printStackTrace();
} catch (IOException e) {
//
TODO Auto-generated catch block
e.printStackTrace();
} catch
(ClassNotFoundException e) {
// TODO Auto-generated catch
block
e.printStackTrace();
}finally{
try
{
fis.close();
} catch (IOException e) {
// TODO
Auto-generated catch
block
e.printStackTrace();
}
}
}
输出结果:
学号:1 姓名:张三 年龄:16
学号:2 姓名:李四 年龄:18
上面我们举得例子很简单,要保存的成员变量要么是基本类型的要么是String类型的。但有时成员变量有可能是引用类型的,这是的情况会复杂一点。那就是当要对某对象进行序列化时,该对象中的引用变量所引用的对象也会被同时序列化,并且该对象中如果也有引用变量的话则该对象也将被序列化。总结说来就是在序列化的时候,对象中的所有引用变量所对应的对象将会被同时序列化。这意味着,引用变量类型也都要实现Serializable接口。当然其他对象的序列化都是自动进行的。所以我们只要保证里面的引用类型是都实现Serializable接口就行了,如果没有的话,会在编译时抛出异常。如果序列化的对象中包含没有实现Serializable的成员变量的话,这时可以使用transient关键字,让序列化的时候跳过该成员变量。使用关键字transient可以让你在序列化的时候自动跳过transient所修饰的成员变量,在反序列化时这些变量会恢复到默认值。