java代码优化(十)——序列化

谨慎地实现序列化接口

实现序列化接口的利与弊:

坏处一是一旦被序列化的类发布,就大大降低类的灵活性。

坏处二是它增增加了bug和安全漏洞的可能性。

坏处三是随着发行版本的增加,相关的测试任务急剧增加。

好处就是将一个对象加入到框架中,当该框架需要依靠序列化来实现对象传输或持久化时,让该对象实现序列化是非常有必要的。例如Date、BigInteger、集合等应该实现serializable接口,而代表活动实体的类比如线程池pool就不应该实现序列化接口。

为了继承而设计的类尽量避免实现serializable接口,但也有例外,比如Component实现序列化接口使得GUI可以被发送、保存和恢复。HttpServlet实现序列化接口使得session会话可以被缓存。Throwable实现序列化接口使得RMI可以将错误信息从远程服务器发送到本地客户端。

内部类不可以被序列化,但是静态成员类可以被序列化。

考虑使用自定义的序列化形式

如果一个对象的物理表示法和它的逻辑表示法相同时,可能就适合使用默认的序列化形式,即使确定了使用默认的序列化形式,也要使用readObject方法来确保它的约束关系和安全性。

如果对象的物理表示法和逻辑表示法有本质的区别时,使用默认的序列化形式有以下缺点:

①该类导出的公共api受限于该类的内部表示法。

②增加了时间和空间的开销。

③它会引起栈溢出。

无论是使用默认的序列化形式还是自定义的序列化形式,都应该在类内部增加一个显示的序列化id,否则将会在序列化类时生成一个uid,增加了内存开销。

因为类导出的共有api就是一个构造器,所以当一个可以被序列化的不可变类包含可变的私有组件时,就需要进行保护性拷贝,检查参数的有效性。

当我们正在编写readObject方法的时候,把它当成一个公有构造器来看待,下面几点是指导性的建议:

①对于引用组件必须为私有的,要保护性的拷贝它们。

②在保护性拷贝之后,需要检查参数的有效性,并抛出invalidObjectException异常。

③如果对象在被反序列化后需要验证,就用ObjectInputValidation接口。

④无论使用直接方式还是间接方式,都不要调用类中的可被覆盖的方法。

对于实例控制,枚举类型优先于readResolve

下面是实现单例模式的例子(饿汉式):

public class Elvis {

	private static final Elvis elvis = new Elvis();
	
	private Elvis(){
	}
	
	public Elvis getInstance(){
		return elvis;
	}
	
}

如果我们让这个类implements serializable接口,那么它就不再是单例的,不论它是默认的序列化形式还是自定义的序列化形式,这时可以使用readResolve特性:它将返回在第一次初始化类是实例化的对象。它将确保这个类是一个singleton。

	private Elvis readResolve() {
		// TODO Auto-generated method stub
		return elvis;
	}

注意:依赖readResolve特性进行实例控制,带有引用类型的实例域必须声明为transient。

使用枚举类型可以保证受控实例的类可以被序列化,并且只会被实例化一次。如果做不到用枚举类型实现实例控制,同时又需要实现可序列化和实例控制的类,就必须要实现readResolve方法,并确保所有的实例域是基本数据类型或者是transient的。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值