Dlaczego enum singleton jest leniwy?
Widziałem odpowiedzi takie jak te , próbowałem wyjaśnić je za pomocą komentarzy i nie byłem usatysfakcjonowany przykładami tutaj .
Może czas na to konkretne pytanie ...
Dlaczego implementacja enum singleton jest nazywana leniwą ?
public enum EnumLazySingleton {
INSTANCE;
EnumLazySingleton() {
System.out.println("constructing: " + this);
}
public static void touchClass() {}
}
Czym różni się od gorliwego wdrożenia?
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() {}
}
Oba zainicjują instancję bez dostępu INSTANCE/getInstance()- np touchClass(). Call .
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();
}
}
Wynik:
sleeping for 5 sec...
touching BasicEagerSingleton
constructing: BasicEagerSingleton@7bfcd12c
touching EnumLazySingleton
constructing: INSTANCE
Teraz możemy powiedzieć, że oboje są leniwi . Co więc jest chętne ?
Jest jasne, dlaczego (np.) Sposób „podwójnego sprawdzania blokowania” jest w rzeczywistości leniwy (i niechlujny oraz powolny). Ale jeśli enum jest leniwe, to każdy singleton jest leniwy z powodu nieuniknionego ładowania klas - w rzeczywistości wszystko jest leniwe. W którym momencie to rozróżnienie przestanie mieć sens?
Odpowiedzi
Pierwsze dwie połączone odpowiedzi (autorstwa Petera Lawreya i Joachima Sauera ) zgadzają się, że wyliczenia nie są inicjowane leniwie. Odpowiedzi w trzecim łączu są po prostu błędne, co do leniwej inicjalizacji.
Zalecenie używania wyliczeń jako singletonów pochodzi z Effective Java Josha Blocha. Warto zauważyć, że rozdział o enum singletons nie wspomina o lenistwie. Istnieje późniejszy rozdział poświęcony leniwej inicjalizacji, w którym również nie wspomina się o wyliczeniach. Rozdział zawiera dwie najważniejsze informacje.
- Jeśli musisz użyć opóźnionej inicjalizacji w celu uzyskania wydajności w polu statycznym, użyj idiomu klasy posiadacza inicjalizacji z opóźnieniem.
- Jeśli musisz użyć inicjalizacji z opóźnieniem w celu uzyskania wydajności w polu wystąpienia, użyj idiomu podwójnego sprawdzenia.
Niewątpliwie wyliczenia byłyby kolejnym idiomem na tej liście, gdyby były w jakikolwiek sposób inicjowane leniwie. W rzeczywistości tak nie jest, chociaż niejasność co do znaczenia leniwej inicjalizacji skutkuje pewnymi błędnymi odpowiedziami, jak pokazuje PO.
Czy mogę postawić na następujące rzeczy:
Próbujesz zidentyfikować 2 „procesy” lub ... "things"(ułatwmy sobie zrozumienie - bo jeśli zacznę mówić „bloki kodu”, brzmi to trudniej) ...
- W pewnym momencie program ładujący klasy zostanie uruchomiony i chciałbyś wiedzieć, co
"things"zostanie wykonane, gdy moduł ładujący klasy załaduje klasę. - W innym momencie wywołanie metody w klasie spowoduje
"thing"uruchomienie / wykonanie innej metody i chciałbyś wiedzieć, która dokładnie (która"processes") się rozpocznie.
Istotne są następujące fakty:
- Inicjatory statyczne są uruchamiane, gdy moduł ładujący klasy ładuje klasę. Program ładujący klasy nie załaduje klasy, dopóki działający kod nie napotka potrzeby jej załadowania (ponieważ została wywołana metoda lub pole), na przykład:
touchClass()- Jeśli pojedyncze wystąpienie KAŻDEJ klasy LUB wyliczonego typu ma
fieldinicjalizację wstaticczęści klasy, zostanie załadowane, gdy tylko `` dotkniesz '' klasy - ponieważ klasa ładująca działa w całościstatic initializationsdla klasy lub wyliczenie podczas ładowania.- Prawdopodobnie leniwe ładowanie (I to jest moja „interpretacja” tego, o co pytasz) miałoby miejsce, gdy wywołanie metody prosi klasę o utworzenie pojedynczej instancji - co może się zdarzyć trochę czasu po „załadowaniu” metody
classlubenum.
Klasa taka jak ta:
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"); }
}
Tutaj z drugiej strony:
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() { }
}
Więc następujący kod wygeneruje dane wyjściowe
LazySingleton.touchClass(); // Prints nothing
EagerEnum.touchClass(); // Prints "Eager Enum Constructed"
LazySingleton.getSingletonInstance(); // Prints "constructing: LazySingleton