Pourquoi enum singleton est paresseux?

Sep 24 2020

J'ai vu des réponses comme celles - ci , j'ai essayé de clarifier via des commentaires et je n'ai pas été satisfait des exemples ici .

Il est peut-être temps pour cette question précise ...

Pourquoi l'implémentation enum singleton est appelée paresseuse ?

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

En quoi est-ce différent d' une mise en œuvre rapide ?

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

Les deux lanceront l'instance sans y accéder INSTANCE/getInstance()- par exemple, appeler 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();
    }
}

Production:

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

Maintenant, nous pouvons dire que les deux sont paresseux . Qu'est-ce qui est impatient alors?

Il est clair comment (par exemple) la méthode de "verrouillage double-vérifié" est en fait paresseuse (et désordonnée, et lente). Mais si enum est paresseux, alors tout singleton est paresseux en raison du chargement de classe inévitable - en fait, tout est paresseux. À quel moment cette distinction cessera-t-elle d'avoir un sens?

Réponses

3 jaco0646 Sep 24 2020 at 19:58

Les deux premières réponses liées (par Peter Lawrey et Joachim Sauer ) conviennent toutes deux que les énumérations ne sont pas initialisées paresseusement. Les réponses dans le troisième lien sont tout simplement fausses sur ce que signifie l'initialisation paresseuse.

La recommandation d'utiliser les énumérations comme singletons provient de Effective Java de Josh Bloch. Notamment, le chapitre sur les singletons enum ne fait aucune mention de la paresse. Il y a un chapitre ultérieur consacré à l'initialisation paresseuse, qui ne fait pas non plus mention d'énumérations. Le chapitre contient deux faits saillants.

  • Si vous devez utiliser l'initialisation différée pour des performances sur un champ statique, utilisez l'idiome de classe de détenteur d'initialisation différée.
  • Si vous devez utiliser l'initialisation différée pour les performances sur un champ d'instance, utilisez l'idiome de double vérification.

Sans aucun doute, les énumérations seraient un autre idiome sur cette liste si elles étaient initialisées paresseusement. En fait, ils ne le sont pas, bien que la confusion sur la signification de l'initialisation paresseuse entraîne des réponses incorrectes, comme le montre l'OP.

Y2020-09 Sep 24 2020 at 18:58

Puis-je parier ce qui suit:

Vous essayez d'identifier 2 "processus" ou ... "things"(rendons cela facile à comprendre - car si je commence à dire "blocs de code", cela semble plus difficile) ...

  • À un moment donné, le chargeur de classe s'exécutera et vous aimeriez savoir ce "things"qui sera exécuté lorsque le chargeur de classe chargera une classe.
  • À un autre moment, l'invocation d'une méthode sur la classe en entraînera une autre "thing"à exécuter / exécuter, et vous voudriez savoir lequel, exactement, (lequel "processes") commencerait.

Les faits suivants sont pertinents:

  • Les initialiseurs statiques sont exécutés lorsque le chargeur de classe charge la classe. Le chargeur de classe ne chargera pas la classe tant que le code qui s'exécute n'aura pas besoin de la charger (car une méthode ou un champ a été appelé) comme:touchClass()
  • Si une instance singleton de SOIT une classe, OU un type énuméré a un fieldqui est en cours d'initialisation dans la staticpartie de la classe, elle sera chargée dès que vous `` touchez '' la classe - car le Class-Loader exécute tout static initializationspour une classe ou enum au chargement.
  • Le chargement paresseux, probablement, (et voici mon "interprétation" de ce que vous demandez) se produirait quand une invocation de méthode demande à la classe de créer une instance de singleton - ce qui pourrait se produire un peu de temps après le "chargement" du classou enum.

Une classe comme la suivante:

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

Ici par contre:

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

Ainsi, le code suivant produirait la sortie

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