Tradicionalmente, um singleton é geralmente implementado como
public class Foo1
{
private static final Foo1 INSTANCE = new Foo1();
public static Foo1 getInstance(){ return INSTANCE; }
private Foo1(){}
public void doo(){ ... }
}
Com o enum do Java, podemos implementar um singleton como
public enum Foo2
{
INSTANCE;
public void doo(){ ... }
}
Por mais incrível que seja a 2ª versão, existem algumas desvantagens?
(Pensei um pouco e responderei minha própria pergunta; espero que você tenha respostas melhores)
Respostas:
Alguns problemas com enum singletons:
Comprometendo-se com uma estratégia de implementação
Normalmente, "singleton" refere-se a uma estratégia de implementação, não a uma especificação de API. É muito raro
Foo1.getInstance()
declarar publicamente que sempre retornará a mesma instância. Se necessário, a implementação deFoo1.getInstance()
pode evoluir, por exemplo, para retornar uma instância por encadeamento.Com
Foo2.INSTANCE
declaramos publicamente que neste caso é o exemplo, e não há nenhuma chance de mudar isso. A estratégia de implementação de ter uma única instância é exposta e comprometida.Este problema não é incapacitante. Por exemplo,
Foo2.INSTANCE.doo()
pode contar com um objeto auxiliar local de encadeamento, para ter efetivamente uma instância por encadeamento.Estendendo a classe Enum
Foo2
estende uma super classeEnum<Foo2>
. Geralmente, queremos evitar super classes; especialmente neste caso, a superclasse forçadaFoo2
não tem nada a ver com o queFoo2
deveria ser. Isso é uma poluição para a hierarquia de tipos de nosso aplicativo. Se realmente queremos uma super classe, geralmente é uma classe de aplicação, mas não podemos,Foo2
a super classe é fixa.Foo2
herda alguns métodos engraçados de instância comoname(), cardinal(), compareTo(Foo2)
, que são apenas confusos paraFoo2
os usuários.Foo2
não pode ter seu próprioname()
método, mesmo que esse método seja desejável naFoo2
interface.Foo2
também contém alguns métodos estáticos engraçadosque parece ser sem sentido para os usuários. Um singleton geralmente não deve ter métodos estáticos pulbicos de qualquer maneira (além do
getInstance()
)Serializability
É muito comum os singletons serem stateful. Esses singletons geralmente não devem ser serializáveis. Não consigo pensar em nenhum exemplo realista em que faça sentido transportar um singleton com estado de uma VM para outra VM; um singleton significa "único dentro de uma VM", não "único no universo".
Se a serialização realmente faz sentido para um singleton com estado, o singleton deve especificar explícita e precisamente o que significa desserializar um singleton em outra VM em que um singleton do mesmo tipo já exista.
Foo2
se compromete automaticamente com uma estratégia simplista de serialização / desserialização. Isso é apenas um acidente esperando para acontecer. Se tivermos uma árvore de dados referenciando conceitualmente uma variável de estado daFoo2
VM1 em t1, por serialização / desserialização, o valor se tornará um valor diferente - o valor da mesma variável daFoo2
VM2 em t2, criando um erro difícil de detectar. Este bug não acontecerá com o unserializableFoo1
silenciosamente.Restrições de codificação
Há coisas que podem ser feitas nas aulas normais, mas proibidas nas
enum
aulas. Por exemplo, acessando um campo estático no construtor. O programador precisa ter mais cuidado, pois está trabalhando em uma aula especial.Conclusão
Ao pegar carona no enum, salvamos 2 linhas de código; mas o preço é muito alto, temos que carregar todas as bagagens e restrições de enums, inadvertidamente herdamos "características" de enum que têm conseqüências não intencionais. A única vantagem alegada - serialização automática - acaba sendo uma desvantagem.
fonte
Constructor<?> c=EnumType.class.getDeclaredConstructors()[0]; c.setAccessible(true); EnumType f=(EnumType)MethodHandles.lookup().unreflectConstructor(c).invokeExact("BAR", 1);
por exemplo, um exemplo muito bom seria :;Constructor<?> c=Thread.State.class.getDeclaredConstructors()[0]; c.setAccessible(true); Thread.State f=(Thread.State)MethodHandles.lookup().unreflectConstructor(c).invokeExact("RUNNING_BACKWARD", -1);
^), testado em Java 7 e Java 8…Uma instância de enum depende do carregador de classes. ou seja, se você tiver um carregador de segunda classe que não tenha o carregador de primeira classe como pai carregando a mesma classe enum, poderá obter várias instâncias na memória.
Amostra de código
Crie a enumeração a seguir e coloque o arquivo .class em um jar sozinho. (é claro que o jar terá a estrutura correta de pacote / pasta)
Agora execute esse teste, certificando-se de que não haja cópias da enumeração acima no caminho da classe:
E agora temos dois objetos representando o valor "enum" mesmo.
Concordo que este é um caso de canto raro e artificial, e quase sempre um enum pode ser usado para um singleton em java. Eu mesmo faço isso. Mas a pergunta feita sobre possíveis desvantagens e vale a pena conhecer essa nota de cautela.
fonte
O padrão enum não pode ser usado para nenhuma classe que lança uma exceção no construtor. Se isso for necessário, use uma fábrica:
fonte