为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以方便地把保存的对象状态再读出来。
二、哪些情况下需要序列化
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过RMI传输对象的时候
三、如何进行序列化(序列化的方式)
3.1 默认方式:实现Serializable接口
Serializable是Java提供的一个序列化接口,它是一个空接口,起标识作用,标识该类具备了序列化和反序列化的能力。举例如下:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "姓名:" + name + ";年龄:" + age;
}
}
通过Serializable方式实现对象的序列化,实现起来非常简单,几乎所有工作都被系统自动完成了。如何进行对象的序列化和反序列化也非常简单,只需要采用ObjectOutputStream和ObjectInputStream即可轻松实现:
// 序列化
User user = new User("aronchen", 28);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test.txt"));
out.writeObject(user);
out.close();
// 反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("test.txt"));
User newUser = (User) in.readObject();
in.close();
上述操作到底发生了什么呢?序列化过程中,user对象中的name和age实例变量的值(aronchen, 28)都被ObjectOutputStream保存在test.txt文件中。反序列化过程中,ObjectInputStream 将test.txt中的字节数据还原为User对象。通过查看test.txt文件内容,发现保存在文件中的内容除了对象的相关信息,还有一些额外的辅助信息,比如字段类型等以便于回复原来的对象。
User类中的serialVersionUID是用来辅助序列化和反序列化的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够被正常的反序列化。
3.2 自定义序列化和反序列化
重写writeObject(java.io.ObjectOutputStream s)和readObject(java.io.ObjectInputStream s)两个方法即可。它与3.1的最大不同在于:3.1中调用默认的序列化方式:defaultWriteObject和反序列化方式:defaultReadObject。而在该方法中用户可以自由控制序列化与反序列化的字段,而且不用管理父类或者子类中的字段。代码如下:
public class User implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
System.out.println("执行序列化动作");
s.defaultWriteObject();
s.writeObject(this.name);
s.writeObject(this.age);
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
System.out.println("执行反序列化动作");
s.defaultReadObject();
this.name = (String) s.readObject();
this.age = (int) s.readObject();
}
@Override
public String toString() {
return "姓名:" + name + ";年龄:" + age;
}
}
在对象序列化的过程中会自动调用writeObject方法,其中的s.defaultWriteObject被调用时,每一个未被标记为transient的实例域都会被序列化;在反序列化的过程中会自动调用readObject方法。
3.3 实现Externalizable接口进行序列化
如果用户希望自己指定序列化的内容,则可以让一个类实现Externalizable接口,此接口定义如下:
public interface Externalizable extends Serializable {
// 指定要保存的属性信息,对象序列化时调用
public void writeExternal(ObjectOutput out) throws IOException ;
// 读取被保存的信息,对象反序列化时调用
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException ;
}
具体实现如下:
public class UserInfo implements Externalizable {
private static final long serialVersionUID = 1L;
private String userName;
private String passWord;
private int userAge;
public UserInfo(){
System.out.println("无参构造函数");
}
public UserInfo(String username,String userPass,int userAge) {
this.userName = username;
this.passWord = userPass;
this.userAge = userAge;
}
//当序列化对象时,该方法自动调用
public void writeExternal(ObjectOutput out) throws IOException{
System.out.println("执行序列化方法");
//可以在序列化时写非自身的变量
Date d = new Date();
out.writeObject(d);
//只序列化userName,userPass变量
out.writeObject(userName);
out.writeObject(passWord);
}
//当反序列化对象时,该方法自动调用
public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException{
System.out.println("执行反序列化方法");
Date date = (Date)in.readObject();
Log.e("hello", "序列化时间:" + date);
this.userName= (String)in.readObject();
this.passWord = (String)in.readObject();
}
public String toString(){
return "用户名: "+ this.userName+";密码:"+ this.passWord +
";年龄:"+ this.userAge;
}
}
注意:实现Externalizable接口的类一定要定义默认构造函数,因为反序列化时会调用
无参
构造函数,如果未定义,则出现运行时错误:IllegalAccessException。
四、相关注意事项
a)序列化时,只对对象的状态进行保存,而不管对象的方法;
b)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
c)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化(因此被引用对象所属的类也应该实现序列化);
d)并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:
1.安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行rmi传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。
2.资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现。
e) 如果超类没有提供可访问的无参构造器,子类也不可能做到可序列化。