咱们编写一个实现了Serializable接口(序列化标志接口)的类,Eclipse立刻就会给一个黄色警告:须要增长一个Serial Version ID.为何要增长?它是怎么计算出来的?有什么用?下面咱们来解释一下。java
类实现Serializable接口的目的是为了可持续化,好比网络传输或本地存储,为系统的分布和异构部署提供先决支持条件。如果没有序列化,如今咱们熟悉的远程调用、对象数据库都不可能存在,咱们来看一个简单的序列化类:数据库
public class Person implements Serializable{网络
private String name;分布式
/*name属性的get/set方法省略*/
工具
}
对象
这是一个简单的javabean,实现了Serializable接口,能够在网络上传输,也能够本地存储而后读取。这里咱们以java的消息服务方式传递对象(即经过网络传递一个对象),定义在消息队列中的数据类型为ObjectMessage,首先定义一个消息的生产者(Producer),代码以下:继承
public class Producer{接口
public static void main(String[] args) throws Exception{队列
Person person=new Person();事件
person.setName("死神");
//序列化,保存在磁盘上
SerializationUtils.writeObject(person);
}
}
这里引入了一个工具类SerializationUtils,其做用是对一个类进行序列化和反序列化,并存储到硬盘上,其代码以下:
public class SerializationUtils{
private static String FILE_NAME="c:\obj.bin";
//序列化
public static void writeObject(Serializable s){
try{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(FILE_NAME));
oos.writeObject(s);
oos.close();
}catch(Exception e){
e.printStackTrace();
}
}
public static Object readObject(){
Object obj=null;
//反序列化
try{
ObjectInput input=new ObjectInputStream(new FileInputStream(FILE_NAME));
obj=input.readObject();
input.close();
}catch(Exception e){
e.printStackTrace();
}
return obj;
}
}
经过对象序列化过程,把一个对象从内存块转化为可传输的数据流,而后经过网络发送到消费者那里,并进行序列化,生成实例对象,代码以下:
public class Consumer{
public static void main(String[] args){
//反序列化
Person p=new (Person) SerializationUtils.readObject();
System,out,println("name="+p.getName);
}
}
这是一个反序列化的过程,也就是对象数据流转换为一个实例对象的过程,其运行后的输出结果为:死神。这很容易,是的,这就一个很简单的序列化和反序列化的demo。但此处隐藏一个问题:若是消息的生产者和消息的消费者所参考的类(Person)有差别,会出现何种神奇的事件呢?好比:消息的生产者中的Person类增长一个年龄的属性,而消费者没有增长该属性。为何没有增长,由于这是分布式部署的应用。,你甚至都不知道这个应用部署在何处,特别是经过广播方式发送消息的状况,漏掉一两个订阅者也是很正常的。
在这种序列化和反序列化的类不一致的 情形下,反序列化就会报一个InvalidClassException异常,缘由是序列化和反序列化所对应的类版本发生了变化,JVM不能把数据流转换为实例对象。接着刨根问底:JVM是根据什么来判断一个类版本的呢?
好问题,经过SerialVersionUID,也叫作流标识符,即类的版本定义的,它能够显示申明也能够隐式申明。显示申明格式以下:
private static final long serialVersionUID=xxxxxxL;
而隐式申明则是我不申明。你编译器在编译的时候帮我生成。生成的依据是经过包名、类名、继承关系、非私有的方法和属性,以及参数、返回值等诸多因子计算得出的,极度复杂,基本上计算出来的这个值是惟一的。
serialVersionUID 如何生成已经说明了,那咱们再来看看serialVersionUID的做用。JVM在反序列化时,会比较数据流中的serialVersionUID与类的serialversionUID是否相同,若是相同,则认为类没有发生改变,能够把数据流load为实例对象,若是不相同,对不起,我JVM不干了,抛出个异常InvalidClassExeception给你瞧瞧。这是一个很是好的校验机制,能够保证一个对象即便在网络或磁盘中国“滚过”一次,任然能作到“出淤泥而不染”,完美的实现类的一致性。
可是,有时候咱们须要一点特例场景,例如:个人类改变不大,JVM撒谎说“个人 版本没有变动”,如此,咱们编写的类就实现了向上兼容。咱们修改一下上面的Person类,代码以下:
public class Person implements Serializable{
private static final long serialVersonUID =55799L;
/*其它保持不变*/
刚开始生产者和消费者持有Person类版本一致,都是V1.0,某天生产者的Person类版本变动了,增长了一个年龄的属性,升级为V2.0版本,而因为种种缘由消费者端的Person仍是之前的版本,代码以下:
public class Person implements Serializable{
private static final long serialVersionUID =5799L;
/*age、name的get和set方法省略*/
}
}
此时虽然生产者和消费者对应的类版本不一样,可是显示声明的serialVersionUID相同,反序列化也是能够运行的,所带来的业务问题就是消费者端不能读取新增的业务属性(age而已)
经过此例,咱们的反序列化实现了版本的向上兼容的功能,因此咱们在编写序列化类代码时,随手加上serialversionUID字段,也不会给咱们带来太多的工做量,但它却能够在关键的时候发挥异乎寻常的做用。