Por que enum singleton é preguiçoso?
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
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.
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 nastaticparte da classe, ele será carregado assim que você 'tocar' a classe - porque o Class-Loader executa tudostatic 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
classouenum.
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