收藏了!70%的程序员都不知道的序列化细节,清晰明了( 三 )

并且需要注意的是 , 保护性拷贝在参数有效性检查的前面 , 并且不能使用clone方法进行拷贝对象 。
结论
总而言之 , 每当你编写readObject方法的时候 , 都要这样想:
你正在编写一个公有的构造器 , 无论给它传递什么样的字节流 , 它都必须产生一个有效的实例 。 下面这些经验 , 有助于编写出更加健壮的readObject方法: 对于对象引用域必须保持为私有的 , 要保护性地拷贝这些域中的每个对象 。 不可变类的可变组件就属于这一类别;对于任何约束条件 , 如果检查失败 , 则抛出一个InvalidObjectException异常 。 这些检查动作应该跟在所有的保护性拷贝之后;如果整个对象图在被反序列化之后必须进行验证 , 就应该使用ObjectInputValidation接口;readObject方法中都不要调用可覆盖的方法 , 无论是间接的方式还是直接的方式
使用枚举实现单例问题
针对Singleton , 最简单的一种方式是:
public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { ... } public void leaveTheBuilding() { ... } } 如果类被序列化了 , 不论是采用默认的序列化方式还是采用自定义的序列化方式 , 或者在readObject方法中进行所谓的处理 , 这个类都将不会是单例的了 。 那么针对这种要满足可序列化的单例应该怎样实现?
答案
要满足可序列化的单例 , 有两种方式:
利用readResolve方法:
readResolve特性允许你用readObject创建的实例代替另一个实例 。 对于一个正在被反序列化的对象 , 如果它的类定义了一个readResolve方法 , 并且具备正确的声明 , 那么在反序列化之后 , 新建对象上的readResolve方法就会被调用 。 然后 , 该方法返回的对象引用将被返回 , 取代新建的对象 。 因此 , 在每次反序列化的时候 , 就可以在readResolve方法中返回之前的实例对象 , 这样就可以确保被多次反序列化后的对象也只会有一个 。 示例代码为: // readResolve for instance control - you can do better! private Object readResolve() { // Return the one true Elvis and let the garbage collector // take care of the Elvis impersonator. return INSTANCE; } 复制代码 该方法忽略了被反序列化的对象 , 只返回该类初始化时创建的那个特殊的Elvis实例 。 事实上 , 如果依赖readResolve进行实例控制 , 带有对象引用类型的所有实例域则都必须声明为transient的 。 否则 , 利用readResolve方法实现的单例也会遭受到攻击 。
采用枚举实现:
可以采用枚举实现可序列化的单例 , 这种安全性由JVM提供保障 , 而且代码十分简洁 , 实例域也不需要用transient修饰: // Enum singleton - the preferred approach public enum Elvis { INSTANCE; private String[] favoriteSongs ={ "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
结论
实现可序列化最简单安全的方式是采用枚举的形式 , 应该尽可能采用这种方式 。 如果采用readResolve实现的话 , 可以确保该类的所有实例域都为基本类型 , 或者是transient的 。
【收藏了!70%的程序员都不知道的序列化细节,清晰明了】作者:你听___链接:来源:掘金


推荐阅读