Um Guia para Java Enums
1. Visão geral
Neste artigo, veremos o que são enumerações Java, quais problemas eles resolvem e como alguns dos padrões de design podem ser usados na prática.
The enum keyword was introduced in Java 5. Denota um tipo especial de classe que sempre estende a classejava.lang.Enum. Para a documentação oficial sobre seu uso, dê uma olhada emdocumentation.
As constantes definidas dessa maneira tornam o código mais legível, permitem a verificação em tempo de compilação, documentam antecipadamente a lista de valores aceitos e evitam comportamentos inesperados devido à passagem de valores inválidos.
Aqui está um exemplo rápido e simples de um enum que define o status de um pedido de pizza; o status do pedido pode serORDERED,READY ouDELIVERED:
public enum PizzaStatus {
ORDERED,
READY,
DELIVERED;
}
Além disso, eles vêm com muitos métodos úteis, que você teria que escrever por conta própria se estivesse usando constantes finais estáticas públicas tradicionais.
2. Métodos Enum personalizados
OK, agora que temos um entendimento básico do que são enums e como você pode usá-los, vamos levar nosso exemplo anterior para o próximo nível, definindo alguns métodos de API extras no enum:
public class Pizza {
private PizzaStatus status;
public enum PizzaStatus {
ORDERED,
READY,
DELIVERED;
}
public boolean isDeliverable() {
if (getStatus() == PizzaStatus.READY) {
return true;
}
return false;
}
// Methods that set and get the status variable.
}
3. Comparando tipos de Enum usando o operador “==”
Como os tipos enum garantem que apenas uma instância das constantes exista na JVM, podemos usar com segurança o operador “==” para comparar duas variáveis, como visto no exemplo acima; além disso, o operador "==" fornece segurança em tempo de compilação e tempo de execução.
Vamos primeiro dar uma olhada emat run-time safety no trecho a seguir, onde o operador “==” é usado para comparar os status e umNullPointerException não será lançado se qualquer valor fornull. Por outro lado, o anNullPointerException seria lançado se o método igual fosse usado:
if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED));
if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED);
Quanto acompile time safety, vamos dar uma olhada em outro exemplo onde um enum de um tipo diferente é comparado usando o métodoequals é determinado como verdadeiro - porque os valores de enum egetStatusOs métodos s coincidentemente são os mesmos, mas logicamente a comparação deve ser falsa. Este problema é evitado usando o operador "==".
O compilador sinalizará a comparação como um erro de incompatibilidade:
if(testPz.getStatus().equals(TestColor.GREEN));
if(testPz.getStatus() == TestColor.GREEN);
4. Usando Tipos Enum em Declarações Switch
Os tipos de enum também podem ser usados em instruçõesswitch:
public int getDeliveryTimeInDays() {
switch (status) {
case ORDERED: return 5;
case READY: return 2;
case DELIVERED: return 0;
}
return 0;
}
5. Campos, métodos e construtores em Enums
Você pode definir construtores, métodos e campos dentro de tipos de enumeração que a tornam muito poderosa.
Vamos estender o exemplo acima e implementar a transição de um estágio de uma pizza para outro e ver como podemos nos livrar da instruçãoif e da instruçãoswitch usadas antes:
public class Pizza {
private PizzaStatus status;
public enum PizzaStatus {
ORDERED (5){
@Override
public boolean isOrdered() {
return true;
}
},
READY (2){
@Override
public boolean isReady() {
return true;
}
},
DELIVERED (0){
@Override
public boolean isDelivered() {
return true;
}
};
private int timeToDelivery;
public boolean isOrdered() {return false;}
public boolean isReady() {return false;}
public boolean isDelivered(){return false;}
public int getTimeToDelivery() {
return timeToDelivery;
}
PizzaStatus (int timeToDelivery) {
this.timeToDelivery = timeToDelivery;
}
}
public boolean isDeliverable() {
return this.status.isReady();
}
public void printTimeToDeliver() {
System.out.println("Time to delivery is " +
this.getStatus().getTimeToDelivery());
}
// Methods that set and get the status variable.
}
O snippet de teste abaixo demonstra como isso funciona:
@Test
public void givenPizaOrder_whenReady_thenDeliverable() {
Pizza testPz = new Pizza();
testPz.setStatus(Pizza.PizzaStatus.READY);
assertTrue(testPz.isDeliverable());
}
6. EnumSet eEnumMap
6.1. EnumSet
OEnumSet é uma implementação especializadaSet destinada a ser usada com tiposEnum.
É uma representação muito eficiente e compacta de uma determinadaSet das constantesEnum quando comparada aHashSet, devido àBit Vector Representation interna que é usada. E fornece uma alternativa segura de tipo para os tradicionais “sinalizadores de bits” baseados emint, permitindo-nos escrever um código conciso que é mais legível e sustentável.
OEnumSet é uma classe abstrata que possui duas implementações chamadasRegularEnumSeteJumboEnumSet, uma das quais é escolhida dependendo do número de constantes no enum no momento da instanciação.
Portanto, é sempre uma boa ideia usar este conjunto sempre que quisermos trabalhar com uma coleção de constantes enum na maioria dos cenários (como subconjunto, adição, remoção e para operações em massa comocontainsAlleremoveAll) e useEnum.values() se quiser apenas iterar sobre todas as constantes possíveis.
No snippet de código abaixo, você pode ver comoEnumSet é usado para criar um subconjunto de constantes e seu uso:
public class Pizza {
private static EnumSet undeliveredPizzaStatuses =
EnumSet.of(PizzaStatus.ORDERED, PizzaStatus.READY);
private PizzaStatus status;
public enum PizzaStatus {
...
}
public boolean isDeliverable() {
return this.status.isReady();
}
public void printTimeToDeliver() {
System.out.println("Time to delivery is " +
this.getStatus().getTimeToDelivery() + " days");
}
public static List getAllUndeliveredPizzas(List input) {
return input.stream().filter(
(s) -> undeliveredPizzaStatuses.contains(s.getStatus()))
.collect(Collectors.toList());
}
public void deliver() {
if (isDeliverable()) {
PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
.deliver(this);
this.setStatus(PizzaStatus.DELIVERED);
}
}
// Methods that set and get the status variable.
}
A execução do seguinte teste demonstrou o poder da implementaçãoEnumSet da interfaceSet:
@Test
public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() {
List pzList = new ArrayList<>();
Pizza pz1 = new Pizza();
pz1.setStatus(Pizza.PizzaStatus.DELIVERED);
Pizza pz2 = new Pizza();
pz2.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz3 = new Pizza();
pz3.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz4 = new Pizza();
pz4.setStatus(Pizza.PizzaStatus.READY);
pzList.add(pz1);
pzList.add(pz2);
pzList.add(pz3);
pzList.add(pz4);
List undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList);
assertTrue(undeliveredPzs.size() == 3);
}
6.2. EnumMap
EnumMap é uma implementação especializadaMap destinada a ser usada com constantes enum como chaves. É uma implementação eficiente e compacta em comparação com sua contraparteHashMape é representada internamente como uma matriz:
EnumMap map;
Vamos dar uma olhada rápida em um exemplo real que mostra como pode ser usado na prática:
public static EnumMap>
groupPizzaByStatus(List pizzaList) {
EnumMap> pzByStatus =
new EnumMap>(PizzaStatus.class);
for (Pizza pz : pizzaList) {
PizzaStatus status = pz.getStatus();
if (pzByStatus.containsKey(status)) {
pzByStatus.get(status).add(pz);
} else {
List newPzList = new ArrayList();
newPzList.add(pz);
pzByStatus.put(status, newPzList);
}
}
return pzByStatus;
}
A execução do seguinte teste demonstrou o poder da implementaçãoEnumMap da interfaceMap:
@Test
public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() {
List pzList = new ArrayList<>();
Pizza pz1 = new Pizza();
pz1.setStatus(Pizza.PizzaStatus.DELIVERED);
Pizza pz2 = new Pizza();
pz2.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz3 = new Pizza();
pz3.setStatus(Pizza.PizzaStatus.ORDERED);
Pizza pz4 = new Pizza();
pz4.setStatus(Pizza.PizzaStatus.READY);
pzList.add(pz1);
pzList.add(pz2);
pzList.add(pz3);
pzList.add(pz4);
EnumMap> map = Pizza.groupPizzaByStatus(pzList);
assertTrue(map.get(Pizza.PizzaStatus.DELIVERED).size() == 1);
assertTrue(map.get(Pizza.PizzaStatus.ORDERED).size() == 2);
assertTrue(map.get(Pizza.PizzaStatus.READY).size() == 1);
}
7. Implementar padrões de design usando enums
7.1. Padrão Singleton
Normalmente, implementar uma classe usando o padrão Singleton não é nada trivial. As enums são uma maneira fácil e rápida de implementar singletons.
Além disso, uma vez que a classe enum implementa a interfaceSerializable nos bastidores, a classe é garantida como um singleton pela JVM, o que ao contrário da implementação convencional, onde temos que garantir que nenhuma nova instância seja criada durante desserialização.
No snippet de código abaixo, vemos como podemos implementar o padrão singleton:
public enum PizzaDeliverySystemConfiguration {
INSTANCE;
PizzaDeliverySystemConfiguration() {
// Initialization configuration which involves
// overriding defaults like delivery strategy
}
private PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL;
public static PizzaDeliverySystemConfiguration getInstance() {
return INSTANCE;
}
public PizzaDeliveryStrategy getDeliveryStrategy() {
return deliveryStrategy;
}
}
7.2. Padrão de Estratégia
Convencionalmente, o padrão de estratégia é escrito por meio de uma interface implementada por diferentes classes.
Adicionar uma nova estratégia significa adicionar uma nova classe de implementação. Com enums, isso é alcançado com menos esforço, adicionando uma nova implementação significa definir apenas outra instância com alguma implementação.
O trecho de código abaixo mostra como implementar o padrão de estratégia:
public enum PizzaDeliveryStrategy {
EXPRESS {
@Override
public void deliver(Pizza pz) {
System.out.println("Pizza will be delivered in express mode");
}
},
NORMAL {
@Override
public void deliver(Pizza pz) {
System.out.println("Pizza will be delivered in normal mode");
}
};
public abstract void deliver(Pizza pz);
}
Adicione o seguinte método à classePizza:
public void deliver() {
if (isDeliverable()) {
PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
.deliver(this);
this.setStatus(PizzaStatus.DELIVERED);
}
}
@Test
public void givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges() {
Pizza pz = new Pizza();
pz.setStatus(Pizza.PizzaStatus.READY);
pz.deliver();
assertTrue(pz.getStatus() == Pizza.PizzaStatus.DELIVERED);
}
8. Java 8 e Enums
A classePizza pode ser reescrita em Java 8, e você pode ver como os métodosgetAllUndeliveredPizzas()egroupPizzaByStatus() se tornam tão concisos com o uso de lambdas e as APIsStream:
public static List getAllUndeliveredPizzas(List input) {
return input.stream().filter(
(s) -> !deliveredPizzaStatuses.contains(s.getStatus()))
.collect(Collectors.toList());
}
public static EnumMap>
groupPizzaByStatus(List pzList) {
EnumMap> map = pzList.stream().collect(
Collectors.groupingBy(Pizza::getStatus,
() -> new EnumMap<>(PizzaStatus.class), Collectors.toList()));
return map;
}
9. Representação JSON da Enum
Usando as bibliotecas Jackson, é possível ter uma representação JSON dos tipos de enumeração como se fossem POJOs. O trecho de código abaixo mostra as anotações de Jackson que podem ser usadas para o mesmo:
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PizzaStatus {
ORDERED (5){
@Override
public boolean isOrdered() {
return true;
}
},
READY (2){
@Override
public boolean isReady() {
return true;
}
},
DELIVERED (0){
@Override
public boolean isDelivered() {
return true;
}
};
private int timeToDelivery;
public boolean isOrdered() {return false;}
public boolean isReady() {return false;}
public boolean isDelivered(){return false;}
@JsonProperty("timeToDelivery")
public int getTimeToDelivery() {
return timeToDelivery;
}
private PizzaStatus (int timeToDelivery) {
this.timeToDelivery = timeToDelivery;
}
}
Podemos usar o Pizza e PizzaStatus da seguinte maneira:
Pizza pz = new Pizza();
pz.setStatus(Pizza.PizzaStatus.READY);
System.out.println(Pizza.getJsonString(pz));
para gerar a seguinte representação JSON do statusPizzas:
{
"status" : {
"timeToDelivery" : 2,
"ready" : true,
"ordered" : false,
"delivered" : false
},
"deliverable" : true
}
Para obter mais informações sobre serialização / desserialização JSON (incluindo personalização) de tipos de enum, consulteJackson – Serialize Enums as JSON Objects.
10. Conclusão
Neste artigo, exploramos o enum do Java, do básico da linguagem a casos de uso do mundo real mais avançados e interessantes.
Trechos de código deste artigo podem ser encontrados no repositórioGithub.