1. Visão Geral
Neste artigo, veremos o que são enumerações Java, quais problemas elas resolvem e como usar enumerações Java para implementar alguns padrões de projeto na prática.
A palavra-chave enum foi introduzida em java5 para indicar um tipo especial de classe, que sempre herda a classe java.lang.Enum, e você pode verificar sua documentação oficial para obter mais conteúdo.
As enumerações são freqüentemente comparadas com constantes, provavelmente porque realmente usamos enumerações em grandes números para substituir constantes. Então, quais são as vantagens dessa abordagem?
Constantes definidas desta forma tornam o código mais legível, permitem verificações em tempo de compilação, pré-gravam uma lista de valores aceitáveis e evitam comportamento inesperado devido a valores inválidos passados.
O exemplo a seguir define o status de um pedido de pizza do tipo enumerado simples. Existem três estados de PEDIDO, PRONTO e ENTREGUE:
package shuang.kou.enumdemo.enumtest;
publicenum PizzaStatus {
ORDERED,
READY,
DELIVERED;
}
Para simplificar, evitamos definir constantes por meio do código acima. Colocamos todas as constantes relacionadas ao status do pedido de pizza em um tipo enumerado.
System.out.println(PizzaStatus.ORDERED.name());//ORDERED
System.out.println(PizzaStatus.ORDERED);//ORDERED
System.out.println(PizzaStatus.ORDERED.name().getClass());//class java.lang.String
System.out.println(PizzaStatus.ORDERED.getClass());//class shuang.kou.enumdemo.enumtest.PizzaStatus
2. Método de enumeração personalizado
Agora que temos um entendimento básico do que são enums e como usá-los, vamos levar o exemplo anterior a um novo nível, definindo alguns métodos de API adicionais em enums:
publicclass Pizza {
private PizzaStatus status;
publicenum PizzaStatus {
ORDERED,
READY,
DELIVERED;
}
public boolean isDeliverable() {
if (getStatus() == PizzaStatus.READY) {
returntrue;
}
returnfalse;
}
// Methods that set and get the status variable.
}
3. Use == para comparar os tipos enumerados
Uma vez que o tipo de enumeração garante que haja apenas uma instância constante na JVM, podemos usar com segurança o operador "==" para comparar duas variáveis, conforme mostrado no exemplo acima; além disso, o operador "==" pode fornecer compilar - Segurança em tempo e execução.
Primeiro, vamos dar uma olhada na segurança de tempo de execução no seguinte trecho de código, onde o operador "==" é usado para comparar estados e se ambos os valores forem nulos, NullPointerException não será lançado. Pelo contrário, se o método equals for usado, uma NullPointerException será lançada:
if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED));
if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED);
Para segurança em tempo de compilação, vamos olhar outro exemplo. Dois tipos de enumeração diferentes são comparados. O resultado da comparação usando o método equal é determinado como verdadeiro, porque o valor de enumeração do método getStatus é consistente com o valor de enumeração de outro tipo, mas logicamente deve ser falso. Este problema pode ser evitado usando o operador ==. Porque o compilador indicará um erro de incompatibilidade de tipo:
if(testPz.getStatus().equals(TestColor.GREEN));
if(testPz.getStatus() == TestColor.GREEN);
4. Use tipos enumerados em instruções switch
public int getDeliveryTimeInDays() {
switch (status) {
case ORDERED: return5;
case READY: return2;
case DELIVERED: return0;
}
return0;
}
5. Atributos, métodos e construtores de tipos de enumeração
Você pode torná-lo mais poderoso definindo atributos, métodos e construtores no tipo de enumeração.
A seguir, vamos estender o exemplo acima para realizar a transição de um estágio da pizza para outro e aprender como se livrar da instrução if e da instrução switch usadas antes:
publicclass Pizza {
private PizzaStatus status;
publicenum PizzaStatus {
ORDERED (5){
@Override
public boolean isOrdered() {
returntrue;
}
},
READY (2){
@Override
public boolean isReady() {
returntrue;
}
},
DELIVERED (0){
@Override
public boolean isDelivered() {
returntrue;
}
};
privateint timeToDelivery;
public boolean isOrdered() {returnfalse;}
public boolean isReady() {returnfalse;}
public boolean isDelivered(){returnfalse;}
public int getTimeToDelivery() {
return timeToDelivery;
}
PizzaStatus (int timeToDelivery) {
this.timeToDelivery = timeToDelivery;
}
}
public boolean isDeliverable() {
returnthis.status.isReady();
}
public void printTimeToDeliver() {
System.out.println("Time to delivery is " +
this.getStatus().getTimeToDelivery());
}
// Methods that set and get the status variable.
}
O código a seguir mostra como funciona:
@Test
public void givenPizaOrder_whenReady_thenDeliverable() {
Pizza testPz = new Pizza();
testPz.setStatus(Pizza.PizzaStatus.READY);
assertTrue(testPz.isDeliverable());
}
6.EnumSet e EnumMap
6.1. EnumSet
EnumSet
É um tipo projetado especialmente para Set
tipos enumerados .
Em HashSet
contraste, devido ao uso de representação vetorial de bits interna, é uma representação Enum
muito eficiente e compacta de um conjunto específico de constantes.
Ele fornece uma alternativa segura para os tipos tradicionais de "sinalizadores de bits" baseados em int, permitindo-nos escrever um código conciso que é mais legível e fácil de manter.
EnumSet
É uma classe abstrata, que tem duas implementações: RegularEnumSet
,, JumboEnumSet
que depende do número de instâncias da seleção constantes de enumeração.
Em muitos cenários, o uso de operações de coleta de constantes de enumeração (como: subsetting, adição, exclusão containsAll
e removeAll
operações em lote) EnumSet
é muito adequado; se você precisar iterar todas as constantes possíveis, use-o Enum.values()
.
publicclass Pizza {
privatestatic EnumSet<PizzaStatus> undeliveredPizzaStatuses =
EnumSet.of(PizzaStatus.ORDERED, PizzaStatus.READY);
private PizzaStatus status;
publicenum PizzaStatus {
...
}
public boolean isDeliverable() {
returnthis.status.isReady();
}
public void printTimeToDeliver() {
System.out.println("Time to delivery is " +
this.getStatus().getTimeToDelivery() + " days");
}
public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> 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.
}
O teste a seguir demonstra os EnumSet
recursos poderosos em determinados cenários:
@Test
public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() {
List<Pizza> 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<Pizza> undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList);
assertTrue(undeliveredPzs.size() == 3);
}
6,2 EnumMap
EnumMap
É uma implementação de mapeamento especializada que usa constantes enum como chaves. Em comparação com sua HashMap
contraparte, é uma implementação eficiente e compacta, e é representada como uma matriz internamente:
EnumMap<Pizza.PizzaStatus, Pizza> map;
Vamos dar uma olhada rápida em um exemplo real que demonstra como usá-lo na prática:
publicstatic EnumMap<PizzaStatus, List<Pizza>>
groupPizzaByStatus(List<Pizza> pizzaList) {
EnumMap<PizzaStatus, List<Pizza>> pzByStatus =
new EnumMap<PizzaStatus, List<Pizza>>(PizzaStatus.class);
for (Pizza pz : pizzaList) {
PizzaStatus status = pz.getStatus();
if (pzByStatus.containsKey(status)) {
pzByStatus.get(status).add(pz);
} else {
List<Pizza> newPzList = new ArrayList<Pizza>();
newPzList.add(pz);
pzByStatus.put(status, newPzList);
}
}
return pzByStatus;
}
O teste a seguir demonstra os EnumMap
recursos poderosos em determinados cenários:
@Test
public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() {
List<Pizza> 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<Pizza.PizzaStatus,List<Pizza>> 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 alguns padrões de design por meio de enumeração
7.1 Modo Singleton
Geralmente, não é fácil implementar o padrão Singleton usando classes, e as enumerações fornecem uma maneira fácil de implementar singletons.
"Java eficaz" e "Java e padrões" recomendam fortemente esse método. Quais são os benefícios de usar esse método para implementar a enumeração?
《Java eficaz》
Esse método é semelhante em função ao método de domínio público, mas é mais conciso, fornece um mecanismo de serialização gratuitamente e evita absolutamente múltiplas instanciações, mesmo em face de serialização complexa ou ataques de reflexão. Embora esse método não tenha sido amplamente adotado, o tipo de enumeração de elemento único se tornou a melhor maneira de implementar Singleton. —- "Effective Java Chinese Edition Second Edition"
"Java e padrões"
Em "Java e Padrões", o autor escreveu que o uso da enumeração para obter o controle de instância única será mais conciso, e o mecanismo de serialização é fornecido gratuitamente, e a JVM fundamentalmente fornece garantia para evitar absolutamente múltiplas instanciações. , maneira eficiente e segura de implementar singletons.
O seguinte snippet de código mostra como usar a enumeração para implementar o modo singleton:
publicenum 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;
}
}
Como usá-lo? Por favor, olhe o seguinte código:
PizzaDeliveryStrategy deliveryStrategy = PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy();
O que é PizzaDeliverySystemConfiguration.getInstance()
obtido através do singleton PizzaDeliverySystemConfiguration
7.2 Modo Estratégia
Geralmente, o padrão de estratégia é implementado por diferentes classes que implementam a mesma interface.
Isso também significa que adicionar uma nova estratégia significa adicionar uma nova classe de implementação. Usando enumerações, essa tarefa pode ser realizada facilmente, adicionar uma nova implementação significa apenas definir outra instância com uma determinada implementação.
O seguinte snippet de código mostra como usar a enumeração para implementar o padrão de estratégia:
publicenum 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);
}
Para Pizza
adicionar o seguinte método:
public void deliver() {
if (isDeliverable()) {
PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
.deliver(this);
this.setStatus(PizzaStatus.DELIVERED);
}
}
Como usá-lo? Por favor, olhe o seguinte código:
@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 enumeração
A classe Pizza pode ser reescrita em Java 8. Você pode ver como os métodos lambda e API Stream tornam o método getAllUndeliveredPizzas()
e groupPizzaByStatus()
tão conciso:
getAllUndeliveredPizzas()
:
public static List<Pizza> getAllUndeliveredPizzas(List<Pizza> input) {
return input.stream().filter(
(s) -> !deliveredPizzaStatuses.contains(s.getStatus()))
.collect(Collectors.toList());
}
groupPizzaByStatus()
:
publicstatic EnumMap<PizzaStatus, List<Pizza>>
groupPizzaByStatus(List<Pizza> pzList) {
EnumMap<PizzaStatus, List<Pizza>> map = pzList.stream().collect(
Collectors.groupingBy(Pizza::getStatus,
() -> new EnumMap<>(PizzaStatus.class), Collectors.toList()));
return map;
}
9. Representação JSON do tipo Enum
Usando a biblioteca Jackson, JSON do tipo de enumeração pode ser expresso como POJO. O seguinte snippet de código mostra as anotações de Jackson que podem ser usadas para o mesmo propósito:
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
publicenum PizzaStatus {
ORDERED (5){
@Override
public boolean isOrdered() {
returntrue;
}
},
READY (2){
@Override
public boolean isReady() {
returntrue;
}
},
DELIVERED (0){
@Override
public boolean isDelivered() {
returntrue;
}
};
privateint timeToDelivery;
public boolean isOrdered() {returnfalse;}
public boolean isReady() {returnfalse;}
public boolean isDelivered(){returnfalse;}
@JsonProperty("timeToDelivery")
public int getTimeToDelivery() {
return timeToDelivery;
}
private PizzaStatus (int timeToDelivery) {
this.timeToDelivery = timeToDelivery;
}
}
Podemos usar Pizza
e da seguinte forma PizzaStatus
:
Pizza pz = new Pizza();
pz.setStatus(Pizza.PizzaStatus.READY);
System.out.println(Pizza.getJsonString(pz));
O status Pizza gerado é exibido no seguinte JSON:
{
"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 enumeração, consulte Jackson-Serialize Enumerations into JSON Objects.
10. Resumo
Neste artigo, discutimos os tipos de enumeração Java, desde o conhecimento básico até aplicativos avançados e cenários de aplicativos práticos, vamos sentir o poder da enumeração.
11. Suplemento
Como mencionamos acima, podemos torná-lo mais poderoso definindo atributos, métodos e construtores em tipos de enumeração.
Deixe-me mostrar um exemplo real. Quando chamamos o código de verificação de SMS, pode haver vários propósitos diferentes. Nós o definimos da seguinte maneira:
publicenum PinType {
REGISTER(100000, "注册使用"),
FORGET_PASSWORD(100001, "忘记密码使用"),
UPDATE_PHONE_NUMBER(100002, "更新手机号码使用");
privatefinalint code;
privatefinal String message;
PinType(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
@Override
public String toString() {
return"PinType{" +
"code=" + code +
", message='" + message + '\'' +
'}';
}
}
uso real:
System.out.println(PinType.FORGET_PASSWORD.getCode());
System.out.println(PinType.FORGET_PASSWORD.getMessage());
System.out.println(PinType.FORGET_PASSWORD.toString());
Resultado:
100001
忘记密码使用
PinType{code=100001, message='忘记密码使用'}
Nesse caso, será muito flexível e conveniente no uso real!