Perché enum singleton è pigro?

Sep 24 2020

Ho visto risposte come queste , ho cercato di chiarire tramite commenti e non sono rimasto soddisfatto dagli esempi qui .

Forse è il momento per questa domanda specifica ...

Perché l'implementazione di enum singleton è chiamata lazy ?

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

Come è diverso da desideroso implementazione?

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() {}
}

Entrambi inizieranno l'istanza senza accedere INSTANCE/getInstance(), ad esempio call 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();
    }
}

Produzione:

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

Ora, possiamo dire che entrambi sono pigri . Cosa è desideroso allora?

È chiaro come (ad esempio) il metodo del "blocco a doppio controllo" sia in realtà pigro (e disordinato e lento). Ma se enum è pigro, qualsiasi singleton è pigro a causa dell'inevitabile caricamento della classe - in effetti, tutto è pigro. A che punto questa distinzione smetterà di avere senso?

Risposte

3 jaco0646 Sep 24 2020 at 19:58

Le prime due risposte collegate (di Peter Lawrey e Joachim Sauer ) concordano sul fatto che le enumerazioni non vengono inizializzate pigramente. Le risposte nel terzo collegamento sono semplicemente sbagliate sul significato di inizializzazione pigra.

La raccomandazione di utilizzare le enumerazioni come singleton proviene da Effective Java di Josh Bloch. In particolare, il capitolo sull'enumerazione singleton non fa menzione della pigrizia. C'è un capitolo successivo dedicato all'inizializzazione pigra, che allo stesso modo non fa menzione di enumerazioni. Il capitolo contiene due punti salienti.

  • Se è necessario utilizzare l'inizializzazione pigra per le prestazioni su un campo statico, utilizzare l'idioma della classe titolare dell'inizializzazione pigra.
  • Se è necessario utilizzare l'inizializzazione lenta per le prestazioni su un campo di istanza, utilizzare l'idioma del doppio controllo.

Indubbiamente, le enumerazioni sarebbero un altro idioma in questa lista se fossero in qualche modo inizializzate pigramente. In realtà non lo sono, sebbene la confusione sul significato di inizializzazione pigra si traduca in alcune risposte errate, come mostra l'OP.

Y2020-09 Sep 24 2020 at 18:58

Posso scommettere quanto segue:

Stai cercando di identificare 2 "processi" o ... "things"(rendiamolo facile da capire - perché se inizio a dire "Blocchi codice", suona più difficile) ...

  • Ad un certo punto verrà eseguito il caricatore di classi e si vorrà sapere cosa "things"verrà eseguito quando il caricatore di classi caricherà una classe.
  • In un altro punto, invocare un metodo sulla classe ne causerà "thing"l'esecuzione / esecuzione di un altro, e vorresti sapere quale, esattamente, (quale "processes") inizierebbe ..

I seguenti fatti sono rilevanti:

  • Gli inizializzatori statici vengono eseguiti quando il caricatore di classi carica la classe. Il programma di caricamento classi non caricherà la classe fino a quando il codice in esecuzione non avrà bisogno di caricarlo (perché è stato richiamato un metodo o un campo) come:touchClass()
  • Se un'istanza singleton di SIA una classe, OPPURE un tipo enumerato ha un fieldche viene inizializzato nella staticparte della classe, verrà caricata non appena 'tocchi' la classe - perché il Class-Loader esegue tutto static initializationsper una classe o enum al caricamento.
  • Caricamento pigro, probabilmente, (E questo è il mio "interpretazione" di ciò che si sta chiedendo) sarebbe accaduto quando un metodo invocazione chiede la classe di creare un'istanza di Singleton - che potrebbe avvenire un po 'di tempo dopo il "carico" del classo enum.

Una classe come la seguente:

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"); }
}

Qui invece:

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() { }
}

Quindi il codice seguente produrrebbe l'output

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