Por que usar qualquer recurso de linguagem de programação? A razão pela qual temos idiomas é para
- Programadores para expressar algoritmos de maneira eficiente e correta em um formato que os computadores podem usar.
- Mantenedores para entender algoritmos que outros escreveram e fazer alterações corretamente .
As enums aumentam a probabilidade de correção e legibilidade sem escrever muito clichê. Se você estiver disposto a escrever um texto padrão, poderá "simular" enumerações:
public class Color {
private Color() {} // Prevent others from making colors.
public static final Color RED = new Color();
public static final Color AMBER = new Color();
public static final Color GREEN = new Color();
}
Agora você pode escrever:
Color trafficLightColor = Color.RED;
O padrão acima tem praticamente o mesmo efeito que
public enum Color { RED, AMBER, GREEN };
Ambos fornecem o mesmo nível de verificação de ajuda do compilador. Boilerplate é apenas mais digitação. Mas economizar muita digitação torna o programador mais eficiente (consulte 1), por isso é um recurso que vale a pena.
Também vale a pena por pelo menos mais um motivo:
Instruções de troca
Uma coisa que a static final
simulação de enum acima não fornece é switch
casos agradáveis . Para tipos de enum, o comutador Java usa o tipo de sua variável para inferir o escopo dos casos de enum, portanto, para o enum Color
acima, você apenas precisa dizer:
Color color = ... ;
switch (color) {
case RED:
...
break;
}
Note que não está Color.RED
nos casos. Se você não usar enum, a única maneira de usar quantidades nomeadas switch
é algo como:
public Class Color {
public static final int RED = 0;
public static final int AMBER = 1;
public static final int GREEN = 2;
}
Mas agora uma variável para conter uma cor deve ter tipo int
. A boa verificação do enum e da static final
simulação pelo compilador se foi. Infeliz.
Um compromisso é usar um membro com valor escalar na simulação:
public class Color {
public static final int RED_TAG = 1;
public static final int AMBER_TAG = 2;
public static final int GREEN_TAG = 3;
public final int tag;
private Color(int tag) { this.tag = tag; }
public static final Color RED = new Color(RED_TAG);
public static final Color AMBER = new Color(AMBER_TAG);
public static final Color GREEN = new Color(GREEN_TAG);
}
Agora:
Color color = ... ;
switch (color.tag) {
case Color.RED_TAG:
...
break;
}
Mas observe, ainda mais clichê!
Usando uma enumeração como um singleton
No boilerplate acima, você pode ver por que uma enum fornece uma maneira de implementar um singleton. Em vez de escrever:
public class SingletonClass {
public static final void INSTANCE = new SingletonClass();
private SingletonClass() {}
// all the methods and instance data for the class here
}
e depois acessá-lo com
SingletonClass.INSTANCE
podemos apenas dizer
public enum SingletonClass {
INSTANCE;
// all the methods and instance data for the class here
}
o que nos dá a mesma coisa. Podemos nos safar disso porque as enums do Java são implementadas como classes completas, com apenas um pouco de açúcar sintático espalhado por cima. Isso é novamente menos elaborado, mas não é óbvio, a menos que o idioma lhe seja familiar. Também não gosto do fato de que você obtém as várias funções enum, mesmo que elas não façam muito sentido para o singleton: ord
e values
etc. (na verdade, há uma simulação mais complicada em Color extends Integer
que isso funcionará com o switch, mas é tão complicado que ainda mais mostra claramente por que enum
é uma ideia melhor.)
Segurança da linha
A segurança do encadeamento é um problema em potencial somente quando singletons são criados preguiçosamente sem travamento.
public class SingletonClass {
private static SingletonClass INSTANCE;
private SingletonClass() {}
public SingletonClass getInstance() {
if (INSTANCE == null) INSTANCE = new SingletonClass();
return INSTANCE;
}
// all the methods and instance data for the class here
}
Se muitos threads chamarem getInstance
simultaneamente enquanto INSTANCE
ainda estiver nulo, qualquer número de instâncias poderá ser criado. Isto é mau. A única solução é adicionar synchronized
acesso para proteger a variável INSTANCE
.
No entanto, o static final
código acima não tem esse problema. Ele cria a instância ansiosamente no tempo de carregamento da classe. O carregamento da classe é sincronizado.
O enum
singleton é efetivamente preguiçoso porque não é inicializado até o primeiro uso. A inicialização do Java também é sincronizada, portanto, vários encadeamentos não podem inicializar mais de uma instância INSTANCE
. Você está recebendo um singleton inicializado preguiçosamente com muito pouco código. O único aspecto negativo é a sintaxe bastante obscura. Você precisa conhecer o idioma ou entender completamente como o carregamento e a inicialização da classe funcionam para saber o que está acontecendo.