建议12:避免用序列化类在构造函数中为不变量(final关键字修饰的变量)赋值
代码1:
public class Persion implements Serializable{
private static final long serialVersionUID = 1L;
public final String name;
public Persion () {
name = "liangpeng";
}
}
将 代码1 通过 ObjectOutputStream 的 writeObject(Object obj) 方法写入到一个文件中。然后将 Persion 的 name 的只修改为 liangpengGood
这时 通过 ObjectInputStream 的 readObject() 方法读取一个对象,这时打印Psersion的name值,您觉得是不是应该输出 liangpengGood,但是输出
结果是 liangpeng
至于为什么下面会进行解释
反序列化时构造函数不会执行
反序列化的执行过程:JVM从数据流中获取一个object对象,然后根据数据流中的类文件描述信息(在序列化时,磁盘的对象文件中包含了类的描述信息,注意是类的描述信息,不
是类)查看,发现是final变量,需要重新计算。于是引用Persion类中的name值,而此时JVM又发现name竟然没有赋值,不用引用,于是它很聪明的不再初始化,保持原值状态,所
以结果就是 liangpeng 了。
下面在说一下即使类实现了 serializable 接口后仍然不能序列化的情况
a:static修饰的字段
b:transient关键字修饰的字段
c:父类没有实现serializable接口,序列化时只会序列化子类不会序列化父类。
(欢迎大家积极补充,一起学习成长)
建议,在定义final变量时,就尽量给定它的初始值。
建议13:避免为final变量复杂赋值
建议12所有的final会被重新赋值,其中的“值”指的是简单的对象。简单的对象包括:8个基本数据类型,以及数组,字符串(字符串情况很复杂,不通过new关键字生成String对
象的情况下,final变量的赋值与基本类型相同),但是不能方法赋值。
其中的原理是这样的,保存到磁盘上(或者网络传输)的对象文件包括两部分:
a:类的描述信息
但是不会保存方法,构造函数,static变量等的具体实现。其他的信息,例如,包路径、继承关系、访问权限,等都会保存。
b:没有使用transient关键字修饰和static关键字修饰的实例变量值(如果是实例变量值,是复杂对象,那么连该对象和关联类信息一起保存,并且递归下去,关联类也必须实现
Serializable接口,否则会出现序列化异常)
总结一下,反序列化时final变量在以下情况下不会被重新赋值:
通过构造函数为final变量赋值
通过方法返回值为final变量赋值
final修饰的属性不是基本类型
建议14:使用序列化类的私有方法巧妙解决部分属性持久化的问题
实现Serializable接口的类,可以在类里定义一个readObject方法
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException{
stream.defaultReadObject();
stream.readInt();
}
和一个writeObject方法
private void writeObject(java.io.ObjectOutputStream stream)
throws IOException {
stream.defaultWriteObject();
stream.writeInt(12);
}
方法签名必须一个字母都不能差。这样,当你调用ObjectOutputStream类把一个对象转换成数据流时,会通过反射检查
被序列化的类是否有writeObject方法,若没有,则有ObjectOutputStream按照默认的规则继续序列化。同样,在从流
数据恢复成实例对象时,也会检查是否有一个私有的readObject方法,如果有,则会通过该方法读取属性值。
注意:stream.writeXX 和 stream.writeXX,分别写入和读取相应的值,类似一个队列,先进先出,如果此处有复杂的数据逻辑,
建议封装Collection对象处理。
建议15:break 万万不可忘
public class SuggestTwo {
public static void main(String[] args) {
String str = "";
int n = 0;
switch(n) {
case 0 : str = "a";
case 1 : str = "b";
default : str = "liangpeng";
}
System.out.println("a="+str);
}
}
这段代码的执行结果是,a=liangpeng。
原因是,每个case语句后面少加了break关键字。程序从 case 0 后面的语句开始执行,知道找到最近的break语句结束,但可惜的是我们程序中
没有break语句,于是程序执行的过程中,str被多次赋值,switch语句执行结束了,于是结果也就如此了。