Implementando Singleton com um Enum (em Java)

178

Eu li que é possível implementar Singletonem Java usando um Enumcomo:

public enum MySingleton {
     INSTANCE;   
}

Mas, como isso funciona? Especificamente, um Objectdeve ser instanciado. Aqui, como está MySingletonsendo instanciado? Quem está fazendo new MySingleton()?

Anand
fonte
24
Quem está fazendo o novo MySingleton () A JVM é.
Sotirios Delimanolis
37
INSTANCEé igual public static final MySingleton INSTANCE = new MySingleton();.
Pshemo
6
ENUM - Um singleton garantido.
Não é um bug
Leia dzone.com/articles/java-singletons-using-enum , por que usar enum e não os outros métodos. Curto: os problemas começam ao usar serialização e reflexão.
Miyagi

Respostas:

203

Este,

public enum MySingleton {
  INSTANCE;   
}

tem um construtor vazio implícito. Torne explícito,

public enum MySingleton {
    INSTANCE;
    private MySingleton() {
        System.out.println("Here");
    }
}

Se você adicionou outra classe com um main()método como

public static void main(String[] args) {
    System.out.println(MySingleton.INSTANCE);
}

Você veria

Here
INSTANCE

enumOs campos são constantes de tempo de compilação, mas são instâncias de seu enumtipo. E eles são construídos quando o tipo de enum é referenciado pela primeira vez .

Elliott Frisch
fonte
13
Você deve acrescentar que por padrão enums têm construtor privado implícito e que a adição explicitamente um construtor privado não é necessário a menos que você realmente tem o código que você precisa para ser executado em que o construtor
Nimrod Dayan
cada campo enum cria uma instância apenas uma vez, portanto, não há necessidade de criar construtor privado.
Pravat Panda
2
enumeração pública MySingleton {INSTANCE, INSTANCE1; } e, em seguida, System.out.println (MySingleton.INSTANCE.hashCode ()); System.out.println (MySingleton.INSTANCE1.hashCode ()); imprime códigos hash diferentes. Isso significa que dois objetos do MySingleton são criados?
scott miles
@scottmiles Sim. Porque você tem duas instâncias. Um singleton, por definição, tem um.
Elliott Frisch
O privatemodificador não tem significado para um enumconstrutor e é totalmente redundante.
Dmitri Nesteruk
76

Um enumtipo é um tipo especial de class.

Seu enumserá realmente compilado para algo como

public final class MySingleton {
    public final static MySingleton INSTANCE = new MySingleton();
    private MySingleton(){} 
}

Quando seu código for acessado pela primeira vez INSTANCE, a classe MySingletonserá carregada e inicializada pela JVM. Esse processo inicializa o staticcampo acima uma vez (preguiçosamente).

Sotirios Delimanolis
fonte
Essa classe também deve conter constructor privado ()? Eu acho que seria
Yasir Shabbir Choudhary
public enum MySingleton { INSTANCE,INSTANCE1; }e depois System.out.println(MySingleton.INSTANCE.hashCode()); System.out.println(MySingleton.INSTANCE1.hashCode());imprime códigos de hash diferentes. Isso significa que dois objetos do MySingleton são criados?
Scott Miles
2
@ scottmiles Sim, são apenas duas enumconstantes de diferença .
Sotirios Delimanolis 26/10/16
2
@caf, com certeza. Veja aqui: docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.3
Sotirios Delimanolis
@SotiriosDelimanolis, essa abordagem com enum thread é segura?
ABG
63

Neste livro de práticas recomendadas de Java de Joshua Bloch, é possível explicar por que você deve aplicar a propriedade Singleton a um construtor particular ou a um tipo Enum. O capítulo é bastante longo, portanto, mantendo-o resumido:

Tornar uma classe um Singleton pode dificultar o teste de seus clientes, pois é impossível substituir uma implementação simulada por um singleton, a menos que implemente uma interface que sirva como seu tipo. A abordagem recomendada é implementar Singletons simplesmente criando um tipo de enumeração com um elemento:

// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}

Essa abordagem é funcionalmente equivalente à abordagem de campo público, exceto que é mais concisa, fornece o mecanismo de serialização gratuitamente e oferece uma garantia rígida contra instanciações múltiplas, mesmo em face de sofisticados ataques de serialização ou reflexão.

Embora essa abordagem ainda não tenha sido amplamente adotada, um tipo de enum de elemento único é a melhor maneira de implementar um singleton.

ekostadinov
fonte
Penso que, para tornar um singleton testável, você poderia usar um padrão de estratégia e fazer com que todas as ações do singleton passassem pela estratégia. Então você pode ter uma estratégia de "produção" e um "teste" estratégia ...
Erk
9

Como todas as instâncias enum, o Java instancia cada objeto quando a classe é carregada, com alguma garantia de que é instanciada exatamente uma vez por JVM . Pense noINSTANCE declaração como um campo final estático público: Java instancia o objeto na primeira vez em que a classe é referida.

As instâncias são criadas durante a inicialização estática, definida na Especificação de Linguagem Java, seção 12.4 .

Pelo que vale, Joshua Bloch descreve esse padrão em detalhes como o item 3 do Effective Java Second Edition .

Jeff Bowman
fonte
6

Como o padrão Singleton é sobre ter um construtor privado e chamar algum método para controlar as instanciações (como alguns getInstance), no Enums, já temos um construtor privado implícito.

Não sei exatamente como a JVM ou algum contêiner controla as instâncias da nossa Enums, mas parece que ela já usa um implícito Singleton Pattern, a diferença é que não chamamos de a getInstance, apenas chamamos de Enum.

Bruno Franco
fonte
3

Como já foi mencionado, até certo ponto, uma enum é uma classe java com a condição especial de que sua definição deve começar com pelo menos uma "constante enum".

Além disso, e que enum não podem ser estendidos ou usados ​​para estender outras classes, um enum é uma classe como qualquer classe e você a utiliza adicionando métodos abaixo das definições constantes:

public enum MySingleton {
    INSTANCE;

    public void doSomething() { ... }

    public synchronized String getSomething() { return something; }

    private String something;
}

Você acessa os métodos do singleton ao longo destas linhas:

MySingleton.INSTANCE.doSomething();
String something = MySingleton.INSTANCE.getSomething();

O uso de um enum, em vez de uma classe, é, como foi mencionado em outras respostas, principalmente sobre uma instanciação segura do thread do singleton e uma garantia de que sempre será apenas uma cópia.

E, talvez, o mais importante, que esse comportamento seja garantido pela própria JVM e pela especificação Java.

Aqui está uma seção da especificação Java sobre como várias instâncias de uma instância enum são impedidas:

Um tipo de enumeração não possui instâncias diferentes daquelas definidas por suas constantes de enumeração. É um erro em tempo de compilação tentar instanciar explicitamente um tipo de enum. O método de clone final no Enum garante que as constantes do enum nunca possam ser clonadas e o tratamento especial pelo mecanismo de serialização garante que instâncias duplicadas nunca sejam criadas como resultado da desserialização. A instanciação reflexiva dos tipos de enum é proibida. Juntos, essas quatro coisas garantem que não existam instâncias de um tipo de enumeração além daquelas definidas pelas constantes de enumeração.

É importante notar que, após a instanciação, qualquer preocupação com a segurança do encadeamento deve ser tratada como em qualquer outra classe com a palavra-chave sincronizada etc.

Erk
fonte