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>)
download

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, Stringconstantes 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 Suitconstantes 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 caseo 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, EASTe 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, NORTHcom 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 Coinconstantes. 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.

EnumA 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 Enumvocê interpretaria essa lista de parâmetros de tipo formal da seguinte maneira:

  • Qualquer subclasse de Enum deve fornecer um argumento de tipo real para Enum. Por exemplo, CoinO cabeçalho do especifica Enum.
  • O argumento de tipo real deve ser uma subclasse de Enum. Por exemplo, Coin é uma subclasse de Enum.
  • Uma subclasse de Enum (como Coin) deve seguir o idioma que fornece seu próprio nome (Coin) como um argumento de tipo real.

Examinar Enumdocumentação Java e você descobrirá que ela substitui java.lang.Objectde 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 porque equals() é 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: