11. 养成良好习惯,显示声明UID
Serial Version ID也叫流标识符(Stream Unique Identifier),即类的版本定义,它可以现实声明也可以隐式声明。显示声明格式如下:
private static final long serialVersionUID = XXXXXL;
而隐式声明则是由编译器在编译的时候帮助生成。生成的依据是通过包名、类名、继承关系、非私有的方法和属性,以及参数、返回值等诸多因子计算得出的,基本上计算出来的这个值是唯一的。
serialVersionUID的作用:JVM 在反序列化时,会比较数据流中的serialVersionUID与类的serialVersionUID是否相同,如果相同,则认为类没有发生改变,可以把数据流 load 为实例对象;如果不同,则会抛出InvalidClassException异常。
这是一个非常好的校验机制,可以保证一个对象即使在网络或磁盘中“滚过”一次,仍能做到“出淤泥而不染”,完美地实现类的一致性。
但是如果我们的类改变不大,希望能实现向上兼容,这时就需要向JVM撒谎,也就是依靠显式声明相同的 serialVersionUID。
通过这种向上兼容,无疑提高了代码的健壮性,并且我们在编写序列化类代码时,随手加上serialVersionUID字段也不会给我们带来太多的工作量,但它却可以在关键时候发挥异乎寻常的作用。
注意:显示声明serialVersionUID可以避免对象不一致,但尽量不要以这种方式向JVM“撒谎”。
示例:
数据对象Person:
V1.0版本:
public class Person implements Serializable {
private static final long serialVersionUID = 55799L;
private String name;
/* 省略 name 属性的 getter/setter 方法 */
}
V2.0版本:
public class Person implements Serializable{
private static final long serialVersionUID = 55799L;
private String name;
private int age;
/* 其他保持不变 */
}
序列化工具类:
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 Producer {
public static void main(String[] args) {
Person person = new Person();
person.setName("Michael");
}
}
// 消费者
public class Consumer {
public static void main(String[] args) {
Person person = (Person) SerializationUtils.readObject();
System.out.println("name = " + person.getName());
}
}
这样一来V2.0版本的Person类即能兼容V1.0版本。