12. 避免用序列化类在构造函数中为不变量赋值 ——《编写高质量代码:改善Java程序的151个建议》

12. 避免用序列化类在构造函数中为不变量赋值

我们知道final标识的属性是不变量,也就是说只能赋值一次,当序列化与反序列化的过程中,涉及到final变量时,就会有些复杂,比如以下情况:

public class Person implements Serializable {
	private static final long serialVersionUID = 91282334L;
	public final String name = "Michael";
}
此时name属性参与了序列化与反序列化的过程,并在反序列化时name属性会重新计算其值(这与static变量不同,static变量压根就没有保存到数据流中),比如name属性在v2.0版本中修改为"Jack",那么反序列化对象的name属性就是“Jack”。 保持新旧对象的final变量相同,有利于代码业务逻辑统一,这是序列化的基本规则之一

以上代码的final属性是一个直接量,在反序列化时就会被重新计算,但如果final的赋值是在构造函数中呢?

public class Person implements Serializable {
	private static final long serialVersionUID = 91282334L;
	public final String name;
	public Person() {
		name = "Michael";
	}
}
这也是我们常用的一种赋值方式,如果采用这种方式,当v2.0版本中将name属性改为”Jack“,反序列后的name属性还会跟着改变吗?

NO,此时反序列化的结果仍然是”Micheal“,这里涉及了反序列化的另一个规则:反序列化时构造函数不会执行


反序列化的过程是这样的:JVM从数据流中获取一个Object对象,然后根据数据流中的类文件描述信息(在序列化时,保存到磁盘的对象文件中包含了类描述信息,注意,不是类)查看,发现是final变量,需要重新计算,于是引用Person类中的name值,而此时JVM又发现name竟然没有赋值,不能引用,于是它很”聪明“地不再初始化,保持原值状态,所以结果就是”Michael“了。

如果使用Java开发过桌面应用,特别是参与过对性能要求较高的项目(比如交易类项目),那么很容易遇到这样的问题。比如一个C/S架构的在线外汇交易系统,要求提供24小时的联机服务,如果在升级的类中有一个final变量是构造函数赋值的,而且新旧版本还发生了变化,则在应用请求热切的过程中(非常短暂,可能只有30s),很可能就会出现反序列化生成的final变量值与新产生的实例值不相同的情况,于是业务异常就产生了,情况严重的话甚至会影响交易数据,那可是天大的事故了。


注意:在序列化类中,不使用构造函数为final变量赋值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值