1.概述
通过序列化机制,我们可以把Java内存中的对象转换成二进制字节流,这样就可以把Java对象存储到磁盘中,或者在网络中传输Java对象。1.1序列化的含义和意义
序列化机制允许将实现序列化的Java对象转换成字节序列,这些自己序列可以保存在磁盘上,或通过网络传输,以备以后重新恢复原来的对象。序列化机制使得对象可以脱离程序的运行而单独存在。对象的序列化(Serilize)指将一个Java对象写入IO流中,与此相反的是对象的反序列化(Deserialize)则指从IO流中恢复该Java对象。
如果需要让某个对象支持序列化机制,则必须让它的类实现下面两个接口之一:
Seriliazable
Externalizable
所有网络上传输的对象的类都应该是可序列化的,比如RMI(远程方法调用)过程中的参数和返回值。
所有需要保存在磁盘中的对象的类都必须是可序列化的,比如Web应用中需要保存到HttpSession或SevletContext属性中的应用。
2.使用对象流实现序列化
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializeTest {
public static void main(String[] args) {
try {
ObjectOutputStream oos=new ObjectOutputStream(
new FileOutputStream("src/com/t/color.txt"));
Color color=new Color("Red");
oos.writeObject(color);//将对象写入输出流
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream ois=new ObjectInputStream(
new FileInputStream("src/com/t/color.txt"));
//从输入流中读取对象并强制转换成Color类
Color col=(Color)ois.readObject();
System.out.println(col.getColor());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Color implements Serializable{
private static final long serialVersionUID = -3398911857061187891L;
private String color;
public Color(String color) {
this.color=color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
PS:反序列化无须通过构造器来初始化Java对象。
如果使用序列化机制向文件中写入多个Java对象,使用反序列化机制恢复对象时必须按实际写入的顺序读取。
3.对象引用的序列化
如果一个类中的成员变量有引用类型(不是基本类型或String类型),那么这个引用类型必须是可序列化的,否则该类不可以序列化,即使它实现了Seriliazable或Externalizable也不能序列化。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializeTest {
public static void main(String[] args) {
try {
ObjectOutputStream oos=new ObjectOutputStream(
new FileOutputStream("src/com/t/color.txt"));
Color color=new Color("Red");
Person person=new Person("lavor_zl", color);
oos.writeObject(color);//将对象写入输出流
oos.writeObject(person);//将对象写入输出流
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream ois=new ObjectInputStream(
new FileInputStream("src/com/t/color.txt"));
//从输入流中读取对象并强制转换成Color类
Color col=(Color)ois.readObject();
System.out.println(col.getColor());
Person per=(Person)ois.readObject();
System.out.println(per.getName());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Color implements Serializable{
/**
*
*/
private static final long serialVersionUID = -3398911857061187891L;
private String color;
public Color(String color) {
this.color=color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
class Person implements Serializable{
/**
*
*/
private static final long serialVersionUID = 3426878037487779564L;
private String name;
private Color color;
public Person(String name,Color color) {
this.name=name;
this.color=color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
Color color=new Color("Red");
Person person=new Person("lavor_zl", color);
oos.writeObject(color);//将对象写入输出流
oos.writeObject(person);//将对象写入输出流
一般情况下,这里创建序列化的顺序是先序列化color对象,在序列化person对象并且将再次序列化person对象所引用的Color对象,但是这里person对象引用的Color类对象刚好是前面已经序列化了的color对象。情况会不一样。
那么Java序列化机制采用的序列化规则到底是怎么样的啊?
1、所有保存到磁盘中的对象都有一个序列化编号。
2、当程序试图序列化一个对象时,程序会检查该对象是否已经被序列化过,只有该对象从未在本次虚拟机中序列化过,系统才会将该对象转换成字节序列并输出。
3、如果每个对象已经被序列化过,程序只是直接输出一个序列化编号,而不是再次重新序列化该对象。
正是基于这些规则,多次序列化一个对象时,只有第一次序列化时才会把该Java对象转换成字节序列输出,即使后面改变了该对象的成员变量,反序列化时改变的成员变量的值也不会被输出。
4.自定义序列化
当我们序列化某个对象时,如果不想序列化该对象的某个成员变量时,可以用transient关键字修饰该成员变量,transient关键字只能修饰成员变量。用transient关键字修饰的成员变量将被完全隔离在序列化机制之外。这样导致在反序列化恢复Java对象时无法获取该变量的值,Java还提供一种自定义序列化机制,通过自定义序列化可以让程序控制如何序列化各成员变量,甚至完全不序列化某些成员变量(与使用transient关键字效果相同)。4.1一种自定义序列化机制
在序列化和反序列化过程中需要特殊处理的类应该提供如下特殊的签名方法,这些特殊的签名方法用来来实现自定义序列化。private void writeObject(java.io.ObjectOutputStream out)throws IOException
private void readObject(java.io.ObjectInputStream in)throws IOException,ClassNotFoundException
private void readObjectNoData()throws ObjectStreamException
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Test implements Serializable{
private static final long serialVersionUID = 6102026641912972144L;
private int id;
private String name;
public Test(int id,String name) {
this.id=id;
this.name=name;
}
private void writeObject(ObjectOutputStream out)throws IOException{
out.writeInt(id);
out.writeChars(name);
}
private void readObject(ObjectInputStream in)throws IOException,ClassNotFoundException{
this.id=in.readInt();
//this.name=in.readLine()也可以,不过readLine()已经不推荐使用
this.name=(String) in.readObject();
}
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;
}
}
PS:readObject()方法读取成员变量的顺序应该和writeObject()存储尘成员变量的顺序一致,否则不能正常恢复该Java对象。
static关键字修饰的成员变量也不可以被序列化,因为它是属于类的而不是属于对象的
还有一种更彻底的自定义序列化机制,它甚至可以在序列化该对象时将该对象替换成其他对象。可以通过一下方法实现
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException
此方法有序列化机制在序列化之前调用,只要该方法存在。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
public class Test implements Serializable{
private int id;
private String name;
public Test(int id,String name) {
this.id=id;
this.name=name;
}
//重写writeReplace()方法,程序在序列化该类对象之前调用该方法
private Object writeReplace()throws ObjectStreamException{
ArrayList<Object> list=new ArrayList<Object>();
list.add(id);
list.add(name);
return list;
}
public static void main(String[] args) {
try{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("replace.txt"));
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("replace.txt"));
Test test=new Test(1, "lavor_zl");
oos.writeObject(test);
ArrayList<Object> list=(ArrayList<Object>) ois.readObject();
System.out.println(list);//输出:[1, lavor_zl]
}catch(Exception e){
e.printStackTrace();
}
}
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;
}
}
与writeReplace()相对,序列化机制还有一个特殊的方法,它可以实现保护性的复制整个对象
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException
该方法紧跟着readObject()方法之后调用,该方法返回值将会替代原来的反序列化对象。
readResolve()方法在序列化单例类、枚举类时尤其有用。
4.2另一种自定义序列化机制
Java还提供另一种自定义序列化机制,这种序列化方式完全由程序员决定存储和恢复对象数据。要实现该目标,Java类必须实现Externalizable接口。void writeExternal(ObjectOutput out):需要序列化的类实现该方法来保存对象的状态
void readExternal(ObjectInput in):需要序列化的类通过该方法来实现反序列化