Implementando Singleton com um Enum (em Java)

Eu li que é possível implementar o Singleton em Java usando um Enum como:

 public enum MySingleton { INSTANCE; } 

Mas como funciona o acima? Especificamente, um Object precisa ser instanciado. Aqui, como o MySingleton está sendo instanciado? Quem está fazendo o new MySingleton() ?

   

Este,

 public enum MySingleton { INSTANCE; } 

tem um construtor vazio implícito. Vamos ser explícitos,

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

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

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

Você veria

 Here INSTANCE 

enum fields são constantes de tempo de compilation, mas são instâncias do tipo enum . E eles são construídos quando o tipo enum é referenciado pela primeira vez.

Neste livro de boas práticas de Java , Joshua Bloch, você pode descobrir por que você deve impor a propriedade Singleton com um construtor privado ou um tipo Enum. O capítulo é bastante longo, portanto, mantendo-o resumido:

Tornar uma class um Singleton pode dificultar o teste de seus clientes, já que é impossível replace uma implementação simulada por um singleton, a menos que ela implemente uma interface que sirva como seu tipo. A abordagem recomendada é implementar Singletons simplesmente criando um tipo enum 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 gratuitamente o mecanismo de serialização e fornece uma garantia rígida contra a instanciação múltipla, mesmo diante de sofisticados ataques de serialização ou reflection.

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

Um tipo de enum é um tipo especial de tipo de class .

Sua declaração de enum realmente compila para algo como

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

Quando seu código acessa INSTANCE pela primeira vez, a class MySingleton será carregada e inicializada pela JVM. Este processo inicializa o campo static acima uma vez (preguiçosamente).

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

As instâncias são criadas durante a boot estática, que é definida na Especificação da linguagem Java, seção 12.4 .

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

Como o Singleton Pattern é 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.

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

Como tem sido, até certo ponto, mencionado antes, um enum é uma class java com a condição especial de que sua definição deve começar com pelo menos uma “constante enum”.

Parte disso, é uma class como qualquer class e você a usa adicionando methods abaixo das definições constantes:

 public enum MySingleton { INSTANCE; public void doSomething() { ... } public synchronized String getSomething() { return something; } private String something; } 

Você acessa os methods do singleton ao longo destas linhas:

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

O uso de um enum, em vez de uma class, é, como foi mencionado em outras respostas, principalmente sobre uma instanciação do singleton segura para threads 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 de enum são evitadas:

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

Vale a pena notar que, após a instanciação, quaisquer preocupações com segurança de thread devem ser tratadas como em qualquer outra class com a palavra-chave sincronizada, etc.