初始化占位符模式的正确实现

16

我有两个“Initialization-on-demand holder idiom”的版本:

  1. http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
  2. http://en.wikipedia.org/wiki/Singleton_pattern#The_solution_of_Bill_Pugh

以上两种方法的主要区别是第一种将 INSTANCE 声明为 private,而第二种将 INSTANCE 声明为 public

请告诉我我应该使用哪一个。


抱歉,在我的应用程序中,我没有找到使用 privatepublic 之间的区别:

public class Singleton {
    private int x;
    public int getX() {
        return x;
    }

    private Singleton () {}

    private static class LazyHolder {
        //both private and public works
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
}

我所做的唯一一件事就是调用类似于 Singleton.getInstance().getX() 的东西,因此两种版本都能正常工作。因此我想知道它们的使用情况。

3个回答

16

关于单例模式和"初始化-on-demand holder"模式,有几个需要解释的地方。以下是详细说明:

1)访问修饰符:

通常情况下,如果字段和方法被声明为私有的,则在另一个类中无法访问它们。如果访问类与被访问类在同一包中,则它们必须至少是包私有(没有修饰符)。因此,正确的实现方式应该是:

public class Singleton {
    ...
    private static class LazyHolder {
        static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
}

然而,JLS 6.6.1解释道:

否则,如果成员或构造函数被声明为private,则只有在包含该成员或构造函数的顶层类(§7.6)的主体内出现时才允许访问。

这意味着,即使将字段INSTANCE声明为private,仍然允许从顶层类Singleton内部访问。但编译器必须使用一些技巧来绕过private修饰符:它插入用于获取和设置此类字段的包私有方法。

实际上,无论您将哪个修饰符放在其上都没有关系。如果它是public,仍然无法从除Singleton之外的其他类访问它。然而...我认为包私有访问是最好的。将其公开并没有意义。将其置为private会迫使编译器进行一些技巧。将其设置为包私有反映了您所拥有的:从另一个类访问类成员。

2)如何实现单例:

如果您考虑序列化,单例实现将变得有点困难。Joshu Bloch在他的书“Effective Java”中写了一个关于实现单例的很好的部分。最后,他总结到简单地使用枚举即可,因为Java枚举规范提供了单例所需的每个特性。当然,这不再使用惯用法。

3)考虑设计:

在大多数设计决策中,单例不再有其位置。事实上,如果您必须将单例放入程序中,这可能表明存在设计问题。请记住:单例为某些数据或服务提供了全局访问机制。而这不是面向对象的。


将字段声明为private会让编译器插入包私有方法,这是正确的吗? - chain ro
是的和不是的。编译器必须强制执行,不允许从类外部访问任何私有字段。但他也必须考虑JLS(我引用了)。因此,诀窍是自动添加包私有方法,现在可以从类外部访问这些方法。这些方法放置在内部类中,但仅在需要时,即如果确实存在访问,例如在此示例中。此外,所有对该私有字段的直接访问都将被替换为对这些方法的调用。 - Seelenvirtuose
@Seelenvirtuose,如果LazyHolder没有将INSTANCE设置为final,会有哪些问题? - masT

3
private static class LazyHolder {
    $VISIBILITY static final Singleton INSTANCE = new Singleton();

从消费者的角度来看,$VISIBILITY 是公共的还是私有的并不重要,因为 LazyHolder 类型是私有的。在这两种情况下,变量只能通过静态方法访问。


2
我总是使用默认(即省略)可见性,因为这样可以少输入和阅读一个单词。 - Bohemian

2

我使用第一种方法(私有INSTANCE)是因为通常尽可能使用最窄的范围。但在这种情况下,由于Holder类是私有的,实际上并不重要。然而,假设将来有人决定将Holder类公开,则第二种方法从封装的角度来看可能存在问题(调用者可以绕过getInstance()方法直接访问静态字段)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接