Por que enum singleton é preguiçoso?

Sep 24 2020

Vi respostas como essas , tentei esclarecer por meio de comentários e não fiquei satisfeito com os exemplos aqui .

Talvez seja hora de esta pergunta específica ...

Por que a implementação de enum singleton é chamada de preguiçosa ?

public enum EnumLazySingleton {
    INSTANCE;
    EnumLazySingleton() {
        System.out.println("constructing: " + this);
    }
    public static void touchClass() {}
}

Como isso difere de uma implementação ansiosa ?

public class BasicEagerSingleton {
    private static final BasicEagerSingleton instance = new BasicEagerSingleton();
    public static BasicEagerSingleton getInstance() {
        return instance;
    }
    private BasicEagerSingleton() {
        System.out.println("constructing: " + this);
    }
    public static void touchClass() {}
}

Ambos irão iniciar a instância sem acessar INSTANCE/getInstance()- por exemplo, chamar touchClass().

public class TestSingleton {
    public static void main(String... args) {
        System.out.println("sleeping for 5 sec...");
        System.out.println("touching " + BasicEagerSingleton.class.getSimpleName());
        BasicEagerSingleton.touchClass();
        System.out.println("touching " + EnumLazySingleton.class.getSimpleName());
        EnumLazySingleton.touchClass();
    }
}

Resultado:

sleeping for 5 sec...
touching BasicEagerSingleton
constructing: BasicEagerSingleton@7bfcd12c
touching EnumLazySingleton
constructing: INSTANCE

Agora, podemos dizer que ambos são preguiçosos . O que está ansioso então?

É claro como (por exemplo) o modo de "travamento com verificação dupla" é realmente preguiçoso (e confuso e lento). Mas se enum é preguiçoso, então qualquer singleton é preguiçoso devido ao carregamento de classe inevitável - na verdade, tudo é preguiçoso. Em que ponto essa distinção deixará de fazer sentido?

Respostas

3 jaco0646 Sep 24 2020 at 19:58

As duas primeiras respostas vinculadas (por Peter Lawrey e Joachim Sauer ) concordam que enums não são inicializados lentamente. As respostas no terceiro link estão simplesmente erradas sobre o que significa inicialização lenta.

A recomendação de usar enums como singletons se origina do Java Efetivo de Josh Bloch. Notavelmente, o capítulo sobre os singletons enum não faz nenhuma menção à preguiça. Há um capítulo posterior dedicado à inicialização preguiçosa, que da mesma forma não faz menção a enums. O capítulo contém dois destaques.

  • Se você precisar usar a inicialização lenta para desempenho em um campo estático, use o idioma de classe do portador de inicialização lenta.
  • Se você precisar usar a inicialização lenta para desempenho em um campo de instância, use o idioma de verificação dupla.

Sem dúvida, enums seriam outro idioma nesta lista se fossem inicializados de forma preguiçosa. Na verdade, não são, embora a confusão sobre o significado da inicialização lenta resulte em algumas respostas incorretas, como mostra o OP.

Y2020-09 Sep 24 2020 at 18:58

Posso apostar o seguinte:

Você está tentando identificar 2 "processos" ou ... "things"(vamos tornar isso fácil de entender - porque se eu começar a dizer "Blocos de código", soará mais difícil) ...

  • Em algum ponto, o carregador de classes será executado e você gostaria de saber o que "things"será executado quando o carregador de classes carregar uma classe.
  • Em outro ponto, a invocação de um método na classe fará com que outro "thing"seja executado / executado, e você gostaria de saber qual, exatamente, (qual "processes") iniciaria.

Os seguintes fatos são relevantes:

  • Os inicializadores estáticos são executados quando o carregador de classes carrega a classe. O carregador de classe não carregará a classe até que o código em execução encontre a necessidade de carregá-la (porque um método ou campo foi chamado), como:touchClass()
  • Se uma instância singleton de OU uma classe, OU um tipo enumerado tem um fieldque está sendo inicializado na staticparte da classe, ele será carregado assim que você 'tocar' a classe - porque o Class-Loader executa tudo static initializationspara uma classe ou enum ao carregar.
  • O carregamento lento, provavelmente, (e esta é a minha "interpretação" sobre o que você está perguntando) aconteceria quando uma invocação de método pede à classe para criar uma instância singleton - o que pode acontecer um bom tempo após o "carregamento" do classou enum.

Uma classe como a seguinte:

public class LazySingleton
{
    // At time of class-loading, this singleton is set to 'null'
    private static singleton = null;

    // This is a method that will not be invoked until it is called by
    // some other code-block (some other "thing")...  When "touchClass()"
    // is called, the singleton instance is not created.
    public static LazySingleton retrieveSingleton()
    {
        if (singleton == null) singleton = new LazySingleton();
        return singleton;
    }

    // DOES NOTHING...  The Singleton is *not* loaded, even though the
    // Class Loader has already loaded this Java ".class" file
    // into memory.
    public static void touchClass() { }

    private LazySingleton()
    { System.out.println("constructing: LazySingleton"); }
}

Por outro lado:

public enum EagerEnum
{
  // The class loader will run this constructor as soon as this 'enum'
  // is loaded from a '.class' file (in JAR or on disk) into memory
  MyEnumConstant();

  private EagerEnum()
  { System.out.println("Eager Enum Constructed"); }

  // This will cause the Class Loader to Load this enum from the
  // Java ".class" File immediately, and the "MyEnumConstant" will
  // also have to be loaded - meaning the constructor will be called.
  public static void touchEnum() { }
}

Portanto, o código a seguir produziria a saída

LazySingleton.touchClass();            // Prints nothing
EagerEnum.touchClass();                // Prints "Eager Enum Constructed"
LazySingleton.getSingletonInstance();  // Prints "constructing: LazySingleton