第七十七条 对于实例控制,枚举类型优于readResolve

我们应该知道一点, readResolve() 方法,会在 readObject() 之后被调用,readResolve() 的返回值将会代替 readObject() 中反序列化的对象, readObject() 中反序列化的对象会被丢弃。我们知道,readObject() 会单独开启一个构造器,会打破一些东西。例如单利


    public final class MySingleton {
        private static final MySingleton INSTANCE = new MySingleton();

        private MySingleton() {
        }

        public static MySingleton getInstance() {
            return INSTANCE;
        }
    }

这种写法,很常见。但问题是,如果类 MySingleton 实现了 Serializable 接口,我们把对象序列化到磁盘,然后反序列化时候,会发现,即使是同一份磁盘的内容,反序列化三次,得到的三个对象的地址值指向不同的值,这已经打破了单例模式。上一条说了,反序列化会单独使用一个构造器,与明面上定义的不同。对于这种怎么解决呢?看本条目第一句话,基本可以解决了,重写 readResolve()方法:

     private Object readResolve() throws ObjectStreamException {

      return INSTANCE;
     }

就这样就可以了。

这种方法也有局限性,比如说,里面的成员变量属性都需要用 transient 来修饰对象引用,这样,里面的数据就不显示了。为什么要用这个关键字修饰呢?原因如下,举个栗子

    public class Elvis implements Serializable{
        private static Elvis INSTANCE = new Elvis();
        private Elvis() {
        }
        private Object readResolve(){
            return INSTANCE;
        }
        private String[] favoriteSongs =
                {"Hound Dog", "Heartbreak Hotel"};
        public void printFavorites() {
            System.out.println(Arrays.toString(favoriteSongs));
        }
    }

    public class ElvisStealer implements Serializable {
        static Elvis impersonator;
        private Elvis payload;
        private Object readResolve(){
            impersonator = payload;
            return new String[]{"A Fool Such as I"};
        }
    }

书中给出的例子,"盗用者"类 ElvisStealer ,实例域指向被序列化的Singleton的引用,"盗用者"类就"潜伏"在其中。在序列化流中,用"盗用者"类的实例代替Singleton的非瞬时域。你现在就有了一个循环:Singleton包含"盗用者"类,"盗用者"类则引用该Singleton。当这个Singleton被反序列化时,"盗用者"类的readResolve方法先运行。因此,当"盗用者"的
readResolve方法运行时,它的实例域仍然引用被部分反序列化(并且也还没有被解析)的Singleton。书中给出了编写测试的代码,原理和前两条的原理一样,所以打印出的结果是两个不同的结果

Hound Dog, Heartbreak Hotel]
[A Fool Such as I]

通过将favorites域声明为transient,可以修正这个问题,这就是为什么需要用 transient 关键字修饰的原因了。

如果不用这种方法,还有其他办法吗?java提供的枚举,此时就可以派上用场了,枚举本身就是单利,可以理解为是特殊的单利模式的class类,直接使用即可。

    public enum Elvis {
        INSTANCE;
        private String[] favoriteSongs =
                { "Hound Dog", "Heartbreak Hotel" };
        public void printFavorites() {
            System.out.println(Arrays.toString(favoriteSongs));
        }
    }


我们应该用枚举类型来控制实例,否则,就要必须编写readResolve方法,并保证所有实例域都是基本类型或使用 transient 来修饰。

你可能感兴趣的:(java,effective,注解,枚举,序列化,eadResolve,单例)