序列化:将一个对象编码成字节流。对象按照流一样的方式存入文本文件或者在网络中传输。
反序列化:序列化的逆过程,把文本文件中的流对象数据或者网络中的流对象数据还原成对象。
至于为何要引入序列化?
一般情况,对象是存在于运行的JVM中,JVM停止运行,这些对象就消失。现实运用当中,我们要求JVM停止运行后,能够保存指定的对象的”状态”,(持久化对象)并在重新读取的时候重新读取被保存的对象。再如,在使用远程调用或在网络中传递对象时,都会用到对象的序列化。对象一旦被序列化之后,它的编码就可以从一台正在运行的虚拟机被传递到另一台虚拟上,或者被存储到磁盘上,供以后反序列化时用。序列化技术为远程通信提供了标准的线路级对象表示法,也为JavaBeans组件结构提供了标准的持久化数据格式。
定义一个类,实现Serializable接口,那么这个类定义的对象就可进行序列化。
如果一个类中的某些成员变量,我不想进行序列化我们可以使用transient关键字声明不需要序列化的成员变量。
定义Student类实现了Serrializable接口,并且将age前加transient关键字,使其不参与序列化。
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = -4982435019207183360L;
private String name;
private transient int age ;
private String ID;
public Student() {
super();
}
public Student(String name, int age, String iD) {
super();
this.name = name;
this.age = age;
ID = iD;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getID() {
return ID;
}
public void setID(String iD) {
ID = iD;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", ID=" + ID + "]";
}
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializableDemo {
public static void main(String[] args) throws Exception {
//1.序列化操作
//serialize();
//2.反序列化操作
deserialize();
}
private static void serialize() throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Student.txt"));
Student s = new Student("Jack", 18, "SA001016");
oos.writeObject(s);
oos.close();
}
private static void deserialize() throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Student.txt"));
Student s = (Student) ois.readObject();
System.out.println(s);
}
}
/* 运行结果:
Student [name=Jack, age=0, ID=SA001016]
* */
此种异常属于未序列化异常。未实现此接口的类将无法使其任何状态序列化或反序列化。
Exception in thread “main” java.io.InvalidClassException: cn.Student; local class incompatible:
stream classdesc serialVersionUID = 7172969039201122131, local class serialVersionUID = 4036652964699820801
问题:序列化的对象和类中定义的序列号不一致
原因:可能是因为,类实现了序列化接口,但是没有显式的定义serialVersionUID,由虚拟机自动计算,在修改类后,(或者不同的JVM计算的方式不同,造成serialVersionUID结果不同)反序列化之前的持久化对象,进行serialVersionUID校验,前后不一致而出现的异常。
Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。
如果serialVersionUID在代码中没有定义,则由JVM计算,具体如下:
在序列化写的时候,JVM会依据class的信息,为它计算出一个serialVersionUID,并将serialVersionUID记录在序列化字节中。
在序列化读的时候,JVM也会为class计算出一个serialVersionUID,然后会和字节码中的serialVersionUID做比较。
那么如果两个虚拟机是不同类型的虚拟机,那么计算方法可能就不一样了,于是即使相同的class,serialVersionUID也可能会不同,所以serialVersionUID尽量由我们自己来指定,而不要由虚拟机来计算。
serialVersionUID有两种显示的生成方式:
A、是默认的1L,如:private static final long serialVersionUID = 1L;
B、是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:
private static final long serialVersionUID = -4982435019207183360L;
1)如果虚拟机A中的Class有一个属性,而虚拟机B中的Class,没有这个属性,那么这个属性将被忽略,而不会有异常。
2)如果虚拟机A中的Class没有的属性,而在虚拟机B中多出来的属性,那么这个属性将被赋予一个缺省值,而不会有异常。
3)如果虚拟机A中的AClass有一个属性,在虚拟机B中的AClass也有这个属性,但这个属性的类型变了,
比如说int变成了long,抑或其他的变化,将会有异常:java.io.InvalidClassException:incompatible types for field …
所以由JVM生成serialVersionUID会出现潜在隐患,在单机上可能不会出现问题,但在分布式计算、或者你提供jar供别人使用的时候,这个问题就会暴露。