Este artigo apresenta a diferença entre tipos enumerados e enumerações seguras. Você aprenderá como declarar um enum typesafe e usá-lo em uma instrução switch, e verá como personalizar um enum typesafe adicionando dados e comportamentos. Também daremos uma olhada java.lang.Enum
que é a classe base para todas as enumerações typesafe.
O que você aprenderá neste tutorial Java
- Por que usar enums typesafe e não tipos enumerados
- Como usar enums typesafe em instruções switch
- Como adicionar dados e comportamentos a enums typesafe
- Detalhes e exemplos da classe Enum (Enum
>)
Baixe o código-fonte para obter exemplos neste tutorial. Criado por Jeff Friesen.
Por que usar enums typesafe, não tipos enumerados
Um tipo enumerado especifica um conjunto de constantes relacionadas como seus valores. Os exemplos incluem os dias de uma semana, as direções padrão da bússola norte/sul/leste/oeste, as denominações das moedas de uma moeda e os tipos de token de um analisador léxico.
Os tipos enumerados têm sido tradicionalmente implementados como sequências de constantes inteiras, o que é demonstrado pelo seguinte conjunto de constantes de direção:
static final int DIR_NORTH = 0;
static final int DIR_WEST = 1;
static final int DIR_EAST = 2;
static final int DIR_SOUTH = 3;
Existem vários problemas com esta abordagem:
- Falta segurança de tipo: como uma constante de tipo enumerada é apenas um número inteiro, qualquer número inteiro pode ser especificado onde a constante for necessária. Além disso, adição, subtração e outras operações matemáticas podem ser realizadas nessas constantes; por exemplo,
(DIR_NORTH + DIR_EAST) / DIR_SOUTH
), o que não tem sentido. - O namespace não está presente: As constantes de um tipo enumerado devem ser prefixadas com algum tipo de identificador exclusivo (esperançosamente) (por exemplo,
DIR_
) para evitar colisões com constantes de outro tipo enumerado. - O código é frágil: como as constantes de tipo enumeradas são compiladas em arquivos de classe onde seus valores literais são armazenados (em conjuntos de constantes), a alteração do valor de uma constante requer que esses arquivos de classe e os arquivos de classe de aplicativo que dependem deles sejam reconstruídos. Caso contrário, ocorrerá um comportamento indefinido em tempo de execução.
- Não é informação suficiente: Quando uma constante é impressa, seu valor inteiro é gerado. Esta saída não informa nada sobre o que o valor inteiro representa. Nem sequer identifica o tipo enumerado ao qual a constante pertence.
Você poderia evitar os problemas de “falta de segurança de tipo” e “informação insuficiente” usando java.lang.String
constantes. Por exemplo, você pode especificar static final String DIR_NORTH = "NORTH";
. Embora o valor constante seja mais significativo, String
constantes baseadas em – ainda sofrem de “namespace não presente” e problemas de fragilidade. Além disso, diferentemente das comparações de números inteiros, você não pode comparar valores de string com ==
e !=
operadores (que apenas comparam referências).
Para resolver esses problemas, os desenvolvedores inventaram uma alternativa baseada em classes conhecida como enum typesafe. Infelizmente, esse padrão teve vários desafios. Vou mostrar um exemplo que resume os problemas.
Desafios do padrão enum typesafe
O Suit
class mostra como você pode usar o padrão typesafe enum para introduzir um tipo enumerado que descreve os quatro naipes de cartas:
public final class Suit // Should not be able to subclass Suit.
{
public static final Suit CLUBS = new Suit();
public static final Suit DIAMONDS = new Suit();
public static final Suit HEARTS = new Suit();
public static final Suit SPADES = new Suit();
private Suit() {} // Should not be able to introduce additional constants.
}
Para usar esta classe, você introduziria um Suit
variável e atribuí-la a um dos Suit
constantes de:
Suit suit = Suit.DIAMONDS;
Você pode então querer interrogar suit
em um switch
declaração como esta:
switch (suit)
{
case Suit.CLUBS : System.out.println("clubs"); break;
case Suit.DIAMONDS: System.out.println("diamonds"); break;
case Suit.HEARTS : System.out.println("hearts"); break;
case Suit.SPADES : System.out.println("spades");
}
No entanto, quando o compilador Java encontra Suit.CLUBS
, ele relata um erro informando que uma expressão constante é necessária. Você pode tentar resolver o problema da seguinte maneira:
switch (suit)
{
case CLUBS : System.out.println("clubs"); break;
case DIAMONDS: System.out.println("diamonds"); break;
case HEARTS : System.out.println("hearts"); break;
case SPADES : System.out.println("spades");
}
Mas quando o compilador encontra CLUBS
, ele reportará um erro informando que não foi possível encontrar o símbolo. E mesmo se você colocou Suit
em um pacote, importou o pacote e importou estaticamente essas constantes, o compilador reclamaria que não pode converter Suit
para int
ao encontrar suit
em switch(suit)
. Em relação a cada case
o compilador também informaria que uma expressão constante é necessária.
Java não suporta o padrão enum typesafe com switch
declarações. No entanto, você pode usar o enum typesafe recurso de idioma, que encapsula os benefícios do padrão enquanto resolve seus problemas. Este recurso também suporta switch
.
Como usar enums typesafe em instruções switch
Uma declaração enum typesafe simples em código Java se parece com suas contrapartes nas linguagens C, C++ e C#:
enum Direction { NORTH, WEST, EAST, SOUTH }
Esta declaração usa a palavra-chave enum
para apresentar Direction
como um enum typesafe (um tipo especial de classe), no qual métodos arbitrários podem ser adicionados e interfaces arbitrárias podem ser implementadas. O NORTH
, WEST
, EAST
e SOUTH
constantes enum são implementados como corpos de classe específicos de constantes que definem classes anônimas estendendo o Direction
aula.
Direction
e outras enums typesafe estendem Enum
e herdar vários métodos, incluindo values()
, toString()
e compareTo()
, desta aula. Vamos explorar Enum
mais adiante neste artigo.
A Listagem 1 declara o enum mencionado acima e o utiliza em um switch
declaração. Também mostra como comparar duas constantes enum, para determinar qual constante vem antes da outra constante.
Listagem 1. TEDemo.java (versão 1)
public class TEDemo
{
enum Direction { NORTH, WEST, EAST, SOUTH }
public static void main(String() args)
{
for (int i = 0; i < Direction.values().length; i++)
{
Direction d = Direction.values()(i);
System.out.println(d);
switch (d)
{
case NORTH: System.out.println("Move north"); break;
case WEST : System.out.println("Move west"); break;
case EAST : System.out.println("Move east"); break;
case SOUTH: System.out.println("Move south"); break;
default : assert false: "unknown direction";
}
}
System.out.println(Direction.NORTH.compareTo(Direction.SOUTH));
}
}
A Listagem 1 declara o Direction
typesafe enum e itera sobre seus membros constantes, que values()
retorna. Para cada valor, o switch
instrução (aprimorada para suportar enums typesafe) escolhe o case
que corresponde ao valor de d
e gera uma mensagem apropriada. (Você não prefixa uma constante enum, por exemplo, NORTH
com seu tipo enum.) Por fim, a Listagem 1 avalia Direction.NORTH.compareTo(Direction.SOUTH)
para determinar se NORTH
vem antes SOUTH
.
Compile o código-fonte da seguinte maneira:
javac TEDemo.java
Execute o aplicativo compilado da seguinte maneira:
java TEDemo
Você deve observar a seguinte saída:
NORTH
Move north
WEST
Move west
EAST
Move east
SOUTH
Move south
-3
A saída revela que o herdado toString()
método retorna o nome da constante enum, e isso NORTH
vem antes SOUTH
em uma comparação dessas constantes enum.
Como adicionar dados e comportamentos em enums typesafe
Você pode adicionar dados (na forma de campos) e comportamentos (na forma de métodos) a uma enumeração typesafe. Por exemplo, suponha que você precise introduzir um enum para moedas canadenses e queira que a classe forneça os meios para retornar o número de centavos, moedas de dez centavos, quartos ou dólares contidos em um número arbitrário de centavos. A Listagem 2 mostra como realizar essa tarefa.
Listagem 2. TEDemo.java (versão 2)
enum Coin
{
NICKEL(5), // constants must appear first
DIME(10),
QUARTER(25),
DOLLAR(100); // the semicolon is required
private final int valueInPennies;
Coin(int valueInPennies)
{
this.valueInPennies = valueInPennies;
}
int toCoins(int pennies)
{
return pennies / valueInPennies;
}
}
public class TEDemo
{
public static void main(String() args)
{
if (args.length != 1)
{
System.err.println("usage: java TEDemo amountInPennies");
return;
}
int pennies = Integer.parseInt(args(0));
for (int i = 0; i < Coin.values().length; i++)
System.out.println(pennies + " pennies contains " +
Coin.values()(i).toCoins(pennies) + " " +
Coin.values()(i).toString().toLowerCase() + "s");
}
}
A Listagem 2 declara primeiro um Coin
enum. Uma lista de constantes parametrizadas identifica quatro tipos de moedas. O argumento passado para cada constante representa o número de centavos que a moeda representa.
O argumento passado para cada constante é passado para o Coin(int valueInPennies)
construtor, que salva o argumento no valuesInPennies
campo de instância. Esta variável é acessada de dentro do toCoins()
método de instância. Ele se divide pelo número de centavos passados para toCoin()
de pennies
parâmetro, e este método retorna o resultado, que é o número de moedas na denominação monetária descrita pelo Coin
constante.
Neste ponto, você descobriu que pode declarar campos de instância, construtores e métodos de instância em uma enumeração typesafe. Afinal, um enum typesafe é essencialmente um tipo especial de classe Java.
O TEDemo
aula main()
O método primeiro verifica se um único argumento de linha de comando foi especificado. Este argumento é convertido em um número inteiro chamando o método java.lang.Integer
aula parseInt()
método, que analisa o valor de seu argumento de string em um número inteiro (ou lança uma exceção quando uma entrada inválida é detectada).
Seguindo em frente, main()
itera Coin
constantes. Como essas constantes são armazenadas em um Coin()
variedade, main()
avalia Coin.values().length
para determinar o comprimento desta matriz. Para cada iteração do índice de loop i
, main()
avalia Coin.values()(i)
para acessar o Coin
constante. Ele invoca cada um toCoins()
e toString()
nesta constante, o que prova ainda que Coin
é um tipo especial de aula.
Compile o código-fonte da seguinte maneira:
javac TEDemo.java
Execute o aplicativo compilado da seguinte maneira:
java TEDemo
198
Você deve observar a seguinte saída:
198 pennies contains 39 nickels
198 pennies contains 19 dimes
198 pennies contains 7 quarters
198 pennies contains 1 dollars
O que você precisa saber sobre Enum (Enum>)
O compilador Java considera enum
ser açúcar sintático. Ao encontrar uma declaração enum typesafe, ele gera uma classe cujo nome é especificado pela declaração. Esta classe subclassifica o abstrato Enum
class, que serve como classe base para todas as enumerações typesafe.
Enum
A lista formal de parâmetros de tipo parece horrível, mas não é tão difícil de entender. Por exemplo, no contexto de Coin extends Enum
você interpretaria essa lista de parâmetros de tipo formal da seguinte maneira:
- Qualquer subclasse de
Enum
deve fornecer um argumento de tipo real paraEnum
. Por exemplo,Coin
O cabeçalho do especificaEnum
. - O argumento de tipo real deve ser uma subclasse de
Enum
. Por exemplo,Coin
é uma subclasse deEnum
. - Uma subclasse de
Enum
(comoCoin
) deve seguir o idioma que fornece seu próprio nome (Coin
) como um argumento de tipo real.
Examinar Enum
documentação Java e você descobrirá que ela substitui java.lang.Object
de clone()
, equals()
, finalize()
, hashCode()
e toString()
métodos. Exceto por toString()
todos esses métodos de substituição são declarados final
para que eles não possam ser substituídos em uma subclasse:
clone()
é substituído para evitar que constantes sejam clonadas para que nunca haja mais de uma cópia de uma constante; caso contrário, as constantes não poderiam ser comparadas via==
e!=
.equals()
é substituído para comparar constantes por meio de suas referências. Constantes com as mesmas identidades (==
) deve ter o mesmo conteúdo (equals()
), e identidades diferentes implicam conteúdos diferentes.finalize()
é substituído para garantir que as constantes não possam ser finalizadas.hashCode()
é substituído porqueequals()
é substituído.toString()
é substituído para retornar o nome da constante.
Enum
também fornece seus próprios métodos. Esses métodos incluem o final
compareTo()
(Enum
implementa o java.lang.Comparable
interface), getDeclaringClass()
, name()
e ordinal()
métodos: