Effective Java Item #77 - Serialization of singleton objects - Why should I have to use readResolve?
有效的JavaSyl 77规定,在序列化过程中,我们必须使用EDCOX1×0来保存单独的守护程序。他们使用了这个例子。
1 2 3 4
| public class Elvis implements Serializable{
public static final Elvis INSTANCE = new Elvis ();
private Elvis () { ... }
public void leaveTheBuilding () { ... } |
他们建议使用
If the Elvis class is made to
implement Serializable, the
following readResolve method suffices
to guarantee the singleton property:
1 2 3 4 5
| // 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 ; } |
This method ignores
the deserialized object, returning the
distinguished Elvis instance that was
created when the class was
initialized.
- 现在的问题是序列化再次加载类有两个埃尔维斯的例子吗?
- 如果类只加载一次那么我们应该只有一个ELVIS自静态以来的实例字段未序列化,并且期间未恢复反序列化和
- 其他猫王从哪里来实例出现在垃圾收集清除readresolve(阻止正在转义反序列化过程)。这可以解释吗?
类只被加载一次(除非你和类加载器混在一起,但是类实际上是不同的)。上述代码的反序列化确实创建了一个新的Elvis。您需要使用串行代理来避免这种情况。
虽然有两个Elvis实例,但Elvis.INSTANCE只指向一个。
反序列化在不调用任何可序列化类的构造函数的情况下构造一个对象(但它确实执行最派生的不可序列化基类的no arg构造函数)。
(顺便说一句,您还没有创建类final,因此甚至可以反序列化子类。顺便说一句,您所建议的readResolve方法不会被用于子类,因为它是private。
- @根据对象序列化规范,readResolve方法可以有任何访问修饰符。可以是private。
- @斯蒂芬,我不认为超类中的私有方法是这样的。
- @Stephen C"如果该方法存在,并且可以从被序列化对象的类中定义的方法访问,则通过序列化调用此writereplace方法。"[来自java.io.Serializable],因此对于子类,私有方法将不可访问(它也不会遵循嵌套类的规则…)。
- @Tom-"上述代码的反序列化确实会创建一个新的ELVIS"-您的意思是创建一个ELVIS类型的新对象,并且所有非静态和非瞬态字段都用反序列化过程中的值填充吗?在这种情况下,如何为公共静态最终归档实例提供两个值?
- @Tutysara实例只指向一个实例。Elvis的一个实例是原始实例,另一个实例是通常从反序列化过程中预期的实例,反序列化过程调用了readResolve(除非它是子类)。
- @汤姆-我指的是规范第3.7节中readMethod声明的"签名",上面写着ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;。
- @stephen c"objectinputstream检查对象的类是否定义了readresolve方法。"(我的重点)在API文档中阐明。如果规范不够清晰,我想我可以创建一个规范bug。
- @汤姆-我现在明白你在回答什么了。(我关注的是你所说的private方法,你的真正观点是它在一个子类型中。)但是如果一个readResolve方法是在一个超类中声明的,而不是在你反序列化的实际类中被重写的,那么规范就不那么清晰了。是否值得为此创建一个规范错误…我不知道。(我以前提交过类似的bug,但没有任何变化。我认为这是浪费时间。)
- @stephen c如果它在超类中是可访问的,比如说protected或者一些"包私有/默认访问"的情况,那么readResolve仍然"有效"。/系列化的非规范文档可能需要一点更新——部分仍然在谈论1.1版本作为未来的版本。
- @Tomhawtin在你写的第一句话中说:"这个类只加载一次(除非你和类加载器混在一起,但是类实际上是不同的)"…不是吗"但是instances实际上是不同的"或者我是否遗漏了一些基本的东西?
- @就JVM而言,这是一个完全不同的类。
- @我明白你的意思了
我建议在singleton类中创建一个代理类来保存所有变量。然后您可以拥有访问函数[GetProxyClass()&;setProxyClass()]。使代理类可序列化,然后当您进行序列化或反序列化时,使用代理类并只使用访问器函数来获取或设置它。如果你这样做的话,它就可以减少单身阶级的混乱。
-
Now the question is does serialization loads the class again to have two instance of Elvis?
创建了elvis的新实例(模拟程序),但重载的readResolve方法确保它不会作为ObjectInputStream.readObject()返回的数据结构的一部分返回。
ELVIS模拟程序无法访问(或很快将无法访问),并被垃圾收集。
-
If the class is loaded only once then we should be having only one instance of Elvis since static fields are not serialized and are not restored during deserialization and
理想地是的。在实践中没有。
-
From where does the other Elvis instance comes which is made eligible for garbage collection by readResolve (prevented from escaping the deserialization process). Can this be explained?
反序列化过程从创建第二个ELVIS(模拟程序)开始,但readResolve方法确保没有任何人看到它。
要理解这种情况的方式和原因,您需要了解readResolve()方法在反序列化中扮演的函数,如本文所述。基本上,当readResolve()方法返回INSTANCE时,它会说,"无论您要在我们正在构建的图形中使用冒名器,请使用真正的elvis"。
- @史蒂芬C猫王模仿者变得不可接近,除非对手抢先参考它。
- @Stephen C-"反序列化过程从创建第二个ELVIS(模拟程序)"开始-它是否创建另一个对象并为静态字段实例分配新值?静态字段没有被序列化,我猜它们是在加载类时初始化的。
- @汤姆-我以为我就是这么说的。
- @图蒂萨拉-是的,第二个精灵被创造出来了。不,它不会将新ELVIS分配给静态。普通的对象序列化机制不知道静态,ELVIS的类特定的钩子(例如它的readResolve方法)不分配给INSTANCE。
- @史蒂芬C你说"是(或很快)无法到达",我说不一定如此。(同时,readResolve也(不一定)过载,甚至被覆盖。)
- @汤姆·霍丁-1)我说的是操作代码,在这里,ELVIS类中的readResolve被重写。2)如果有人将elvis的一个子类型定义为打破了singleton模式,我也会将其视为"childclassofelvis"不是singleton,因此可以存在多个实例。既然如此,一个猫王冒名顶替者怎么会变得可以联系到呢?"对手"代码在哪里可以接触到冒名顶替者实例?
- @stephen c private方法不能重写(从jls的重写意义上来说)。我不理解单例子类如何不能破坏单例。返回对Elvis冒名顶替者的引用,直到readResolve返回并处理值为止。
- 单例类的子类(如果可以创建一个子类)不会"继承"它的单例性。ELVIS的子类型只有在具体实现为单独的情况下才是单独的,包括它们自己的静态INSTANCE和getInstance()。(这还涉及为ELVIS提供一个非私有的构造函数…哪种方式打破了单体模式。)这是什么意思?在我看来,这意味着ELVIS冒名顶替者是ELVIS的某些子类的实例,在概念上被允许存在…尽可能多次。他们是猫王的冒名顶替者而不是猫王的冒名顶替者。