Правильная реализация идиомы инициализации по требованию

У меня есть две версии "Инициализация по требованию":

Основное различие между приведенным выше состоит в том, что первая объявила INSTANCE как private, а вторая объявила INSTANCE как public.

Скажите, пожалуйста, какой из них я должен использовать.


Извините, я не нашел разницы между использованием private и общедоступной в моем приложении:

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.getInsance().getX(), поэтому обе версии работают. Поэтому я хочу знать ситуации для их использования.

Ответ 1

Есть несколько вещей, чтобы объяснить о синглонах и идиоматике с инициализацией по требованию. Здесь мы идем:

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 объясняет:

В противном случае, если член или конструктор объявлен приватным, тогда доступ будет разрешено тогда и только тогда, когда оно происходит внутри тела класса верхнего уровня (§7.6) который включает объявление члена или конструктора.

Это означает, что объявление INSTANCE как private все еще разрешает доступ из класса верхнего уровня Singleton. Но компилятор должен сделать некоторые трюки, чтобы обойти частный модификатор: он вставляет частные методы пакета для получения и установки такого поля.

На самом деле, это не имеет значения, какой модификатор вы разместили на нем. Если это общедоступно, к нему по-прежнему нельзя получить доступ из других классов, кроме Singleton. Однако... Я думаю, что доступ к пакету является лучшим. Публикация не имеет смысла. Заставляя его частным образом заставить компилятор делать некоторые трюки. Составление частного пакета отражает то, что у вас есть: доступ к члену класса из другого класса.

2) Как реализовать singleton:

Если вы когда-либо захотите рассмотреть сериализацию, реализация singleton будет немного сложнее. Джошу Блох написал большой раздел в своей книге "Эффективная Ява" о внедрении синглонов. В конце концов, он пришел к выводу, что для этого просто используется перечисление, поскольку спецификация enum Java предоставляет все харектеристики, которые необходимы в отношении одиночных чисел. Конечно, это больше не использует идиому.

3) Учитывая дизайн:

В большинстве проектных решений синглтоны больше не имеют своих мест. Фактически, это может указывать на проблему с дизайном, если вы должны разместить синглтон в своей программе. Имейте в виду: Singletons обеспечивают глобальный механизм доступа к некоторым данным или услугам. И это не ООП.

Ответ 2

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

С точки зрения потребителя не имеет значения, является ли $VISIBILITY общедоступным или приватным, потому что тип LazyHolder является закрытым. Переменная доступна только через статический метод в обоих случаях.

Ответ 3

Я использую номер 1 (частная INSTANCE), потому что вы, как правило, пытаетесь использовать максимально узкую область. Но в этом случае, поскольку класс Гёльдера является частным, это не имеет большого значения. Однако предположим, что кто-то позже решил сделать класс Holder открытым, а номер 2 может быть проблематичным с точки зрения инкапсуляции (вызывающие могут обойти метод getInstance() и напрямую обращаться к статическому полю).