Java 按需初始化持有者习惯用法的正确实现

Java 按需初始化持有者习惯用法的正确实现,java,design-patterns,concurrency,Java,Design Patterns,Concurrency,我有两个版本的“按需初始化持有者习语”: 上面的主要区别是第一个声明为实例为私有,而第二个声明为实例为公共 请告诉我该用哪一种 很抱歉,我没有发现在我的应用程序中使用private和public之间的区别: public class Singleton { private int x; public int getX() { return x; } private Singleton () {} private static cl

我有两个版本的“按需初始化持有者习语”:

  • 上面的主要区别是第一个声明为
    实例
    私有,而第二个声明为
    实例
    公共

    请告诉我该用哪一种


    很抱歉,我没有发现在我的应用程序中使用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.getInance().getX()
    ,这样两个版本都可以工作。 因此,我想知道使用它们的情况

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

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

    我使用数字1(私有实例),因为您通常尝试使用尽可能窄的范围。但是在这种情况下,由于Holder类是私有的,所以它实际上并不重要。但是,假设后来有人决定公开Holder类,那么从封装的角度来看,第2个可能会有问题(调用方可以绕过getInstance()方法,直接访问静态字段)。

    关于Singleton和按需初始化Holder习惯用法,有几点需要解释。我们开始:

    1) 访问修饰符:

    通常,如果字段和方法是私有的,则无法访问另一个类中的字段和方法。如果访问类位于同一个包中,则它们必须至少是包私有的(没有修饰符)。因此,正确的实施方法是:

    public class Singleton {
        ...
        private static class LazyHolder {
            static final Singleton INSTANCE = new Singleton();
        }
        public static Singleton getInstance() {
            return LazyHolder.INSTANCE;
        }
    }
    
    但是,他解释说:

    否则,如果成员或构造函数声明为私有,则访问权限为 当且仅当其发生在顶级类别的主体内时才允许(§7.6) 包含成员或构造函数的声明的

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

    事实上,您在其上放置哪个修改器并不重要。如果它是公共的,那么除了
    Singleton
    之外,仍然无法从其他类访问它。然而。。。我认为包私有访问是最好的。把它公之于众是没有意义的。使其私有化会迫使编译器执行一些技巧。使其包私有反映了您所拥有的:从另一个类访问类成员的权限

    2) 如何实现单例:

    如果你想考虑序列化,单体实现会有点困难。Joshu Bloch在他的书《高效Java》中写了一个关于实现单例的伟大章节。最后,他总结说只需使用一个enum就可以了,因为javaenum规范提供了单例所需的所有字符。当然,这不再使用这个成语了

    3) 考虑到设计:


    在大多数设计决策中,单身者不再有自己的位置。事实上,如果您必须在程序中放置一个单例,它可能表明存在设计问题。请记住:单例提供了对某些数据或服务的全局访问机制。这不是OOP。

    我总是使用两者都不使用-我使用默认(即省略)可见性,因为在代码中键入和读取的字少了一个。将字段声明为private将允许编译器插入包私有方法,是吗?是和否。编译器必须强制不允许从类外访问私有字段。但他也必须考虑JLS(我引用)。所以诀窍是自动添加包私有方法,现在可以从类外部访问这些方法。这些方法被放置在内部类中,但只有在需要时,也就是说,如果确实存在如本例中所示的访问权限,才会放置在内部类中。此外,对该私有字段的所有直接访问都通过对这些方法的调用进行交换。@Seelenvirtuose,如果LazyHolder不将实例作为最终实例,会出现什么问题