序列化的一些注意事项及建议
本文来自《改善java的151个建议》
建议11:养成良好习惯,显示声明UID
我们先写一个序列化与反序列化的工具类SerilizationUtils
public class SerializationUtils {
private static String FILE_NAME="E:/serializable.txt";
public static void writeObject(Serializable s){
try{
ObjectOutputStream oob = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
oob.writeObject(s);
oob.close();
}catch(Exception e){};
}
public static Object readObject(){
Object obj=null;
try{
ObjectInputStream oob = new ObjectInputStream(new FileInputStream(FILE_NAME));
obj = oob.readObject();
oob.close();
}catch(Exception e){};
return obj;
}
}
Person类
public class Person implements Serializable {
/**
* 这里先不显示的声明UID
*/
//private static final long serialVersionUID = 1L;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//
/*private String sex;
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}*/
}
首先定义一个消息的产生者Produce
public class Produce {
public static void main(String[] args){
Person person = new Person();
person.setName("****");
//增加sex后
//person.setSex("男");
//序列化 保存到磁盘上
SerializationUtils.writeObject(person);
System.out.println(person);
}
}
这里执行了之后 person就被序列化到了E:/serializable.txt
然后定义一个消费者
public class Consumer {
public static void main(String[] args) throws Exception{
//反序列化
Person p = (Person)SerializationUtils.readObject();
System.out.println("反序列化得到的值"+p.getName());
//System.out.println("反序列化得到的值"+p.getSex());
}
}
好 运行到这里是没有什么问题的;但是如果我们序列化与反序列化的时候参照的不是一个Person 会出现什么情况 ;
比如我们在序列化之前person还是只有一个属性name 执行produce (序列化)之后;给person增加一个sex属性(注意不要再运行produce (序列化)了);
增加了属性之后 我们运行consumer(反序列化);会出现一个错误
书上说的是InvalidClassException错误;但是我亲自执行报的是上面的错误;
为什么会这样呢?
原因是序列化与反序列化对应的类(person)版本不一致;JVM不能把数据流转换为实例对象;
那JVM是怎么判断一个类的对应版本呢?
是通过SerivalVersionUID ,也就流标识符,即类的版本定义
private static final long serialVersionUID = 1L;
UID可以隐式声明和显示声明;隐式声明是编译器自动生成 基本上市唯一的;如果改变了类 ; 它是UID也会改变
在反序列化的时候 jvm会比较数据流中的UID与当前类(person)是否一致;如果一致说明类没有改动;不一致说明是改动了,这是一个很好的效验机制;
但是;有特殊情况;例如:我的类改变不大,我希望在反序列化的时候也能把它序列化出来。那怎么办呢?
既然是判断UID是否一致,那我们让他们的UID是一致的就可以了,显示声明UID 可以很好的解决这一问题;
建议12:避免用序列化类在构造函数中未不变量final赋值
private static final long serialVersionUID = 1L;
//final 常量是一个直接量 在反序列化的时候就会重新计算 保持final对象的新旧统一 有利于代码业务逻辑统一
public final String testFinal="序列化之前";
// public final String testFinal="序列化之后";
序列化的时候给testFinal赋值是 序列化之前
序列化完成之后 将testFinal改成 序列化之后
然后执行反序列化
//反序列化
Person p = (Person)SerializationUtils.readObject();
System.out.println("反序列化得到的值:"+p.testFinal);
输出来的值是 序列化之前还是之后呢?
输出结果是:反序列化得到的值:序列化之后
为什么呢?我们在序列化的时候 将testFinal序列化成了数据流存在了磁盘中 按理说反序列化的时候 得到的也是 序列化之前的值啊 为什么变成了序列化之后?
这是因为序列化的基本规则之一:保持final新旧对象的相同。有利于代码业务逻辑的统一;也就是说,如果final是一个直接量 则反序列化时候final就会被重新计算;
final变量的另外一种赋值:构造函数赋值
public final String testFinal;
public Person() {
// TODO Auto-generated constructor stub
testFinal="构造函数赋值 之前";
}
序列化之后修改
testFinal="构造函数赋值 之后";
然后进行反序列化;猜想:这里输出应该会是 构造函数赋值 之后 吧? 因为规则一也是这样的啊!
那到底输出什么呢?
反序列化得到的值:构造函数赋值 之前
为什么还是之前 而不是改变之后的呢? 原因是另一个规则
反序列化时构造函数不被执行!
建议13:避免为final变量复杂赋值
总结:反序列化在以下情况不能够被重新赋值
1、通过构造函数为final变量赋值
2、通过方法未final变量赋值
3、final修饰的对象不是基本对象
建议14:使用序列化类的私有方法巧妙解决 部分属性 持久化问题
......
相关文章
- 软件测试工作中的一些经验总结及建议
- 行业寒冬下,给软件测试工程师的一些建议
- FairyGUI的使用技巧和优化建议
- 《MySQL系列》 不建议给MySQL设置Null值的原因?
- 【自动化测试入门】Selenium基础(建议收藏)
- 智能车竞赛接入工程训练竞赛相关事项-建议稿件
- “行业寒冬”,给在座的测试人一些涨薪建议
- 行业寒冬下,给软件测试工程师的一些建议
- 一个数据科学家对商学院的建议
- 关于代码审查的几点建议
- 如何学习安卓,学习安卓的六大建议,成为大佬的必经之路!
- 《Java编码指南:编写安全可靠程序的75条建议》—— 指南2:不要在客户端存储未经加密的敏感数据
- 《Adobe Flash CS4 ActionScript 3.0中文版经典教程》——1.4 对于自学的一些建议
- 哈哈,原来这叫做“松鼠症”……并谈谈我建议的学习方法
- 【Webpack】webpack的基础使用详细总结 下(建议收藏)
- 【微信小程序】小程序使用详细教程(建议收藏)
- 运用计划缓冲的建议
- 基础功能测试的一些实质建议
- 生产环境中使用Docker Swarm的一些建议
- 编写高质量代码:改善Java程序的151个建议(1)
- 负载测试计划的五点建议
- 给程序猿简历的一些建议