Java Enums

Um semáforo tem três cores, nem mais nem menos. Uma semana tem sete dias. Uma camiseta vem em P, M ou G. Quando um valor só pode ser um de uma lista fixa, guardá-lo como texto ("VERDE") ou número (0) abre espaço para erros: "verdee", "Verde", 7. O Java tem um tipo feito para isso, o enum, em que o compilador conhece a lista e recusa qualquer coisa fora dela.

Você vai aprender
  • Como declarar e usar um enum simples.
  • Os métodos que todo enum tem: values(), ordinal(), name() e valueOf() (e a exceção que ele lança).
  • Como usar enums no switch, e por que o compilador avisa quando falta um caso.
  • Como dar atributos, construtor e métodos a um enum (o semáforo com tempos, os planetas com gravidade).

A ideia: uma lista fechada de opções

Pense num formulário de papel com caixinhas para marcar: "Tamanho: ☐ P ☐ M ☐ G". Quem preenche não pode inventar um tamanho "XGG", porque não há caixinha para ele. Compare com um campo em branco "Tamanho: ______", onde cada um escreve de um jeito.

Em Java, uma String é o campo em branco; um enum é a lista de caixinhas. O nome vem de enumeration, "enumeração": uma lista que você enumera, item por item. Cada item é uma constante do enum.


Um enum simples: o semáforo

public class Main {
    public static void main(String[] args) {
        Semaforo sinal = Semaforo.VERDE;
        System.out.println("O sinal está " + sinal);

        if (sinal == Semaforo.VERDE) {
            System.out.println("Pode passar.");
        }

        for (Semaforo s : Semaforo.values()) {
            System.out.println(s.ordinal() + " - " + s.name());
        }
    }
}

enum Semaforo {
    VERDE, AMARELO, VERMELHO
}
enum Semaforo
Declara um tipo novo, Semaforo, como se fosse uma classe, mas com enum no lugar de class.
VERDE, AMARELO, VERMELHO
As constantes: os únicos valores que existem desse tipo. Por convenção, em MAIÚSCULAS, como toda constante.
Semaforo sinal
Uma variável do tipo Semaforo. Ela só aceita uma das três constantes (ou null).
Semaforo.VERDE
Uma constante é acessada pelo nome do enum, como um membro static.
sinal == Semaforo.VERDE
Constantes de enum se comparam com ==: cada uma existe uma única vez na memória.
$ java Main.java
O sinal está VERDE                « o println mostra o nome da constante
Pode passar.
0 - VERDE                         « ordinal = posição na lista, começando em 0
1 - AMARELO
2 - VERMELHO

Os métodos que todo enum ganha

Você não escreveu nenhum método no Semaforo, mas usou três. Todo enum herda de java.lang.Enum e ganha:

MétodoO que devolveExemplo → resultado
Tipo.values()Um array com todas as constantes, na ordem da declaraçãoSemaforo.values() → um array com VERDE, AMARELO e VERMELHO
constante.ordinal()A posição (0, 1, 2…)Semaforo.AMARELO.ordinal() → 1
constante.name()O nome, como textoSemaforo.AMARELO.name() → "AMARELO"
constante.toString()Igual ao name(), a menos que você sobrescrevausado pelo println
Tipo.valueOf("NOME")A constante com aquele nomeSemaforo.valueOf("VERDE") → VERDE
a.compareTo(b)Compara pela ordem da declaraçãoVERDE.compareTo(VERMELHO) → negativo
Não guarde o ordinal()

A posição depende da ordem em que as constantes foram escritas. Se alguém acrescentar PISCANDO entre VERDE e AMARELO, todos os números mudam. Use o ordinal() para mostrar ou ordenar, nunca para gravar num arquivo ou banco de dados; para isso, use o name().

Missão: crie o enum Naipe, com as constantes COPAS, ESPADAS, OUROS e PAUS, nessa ordem. No main, percorra Naipe.values() e mostre cada um no formato posição - nome. A última linha deve ser 3 - PAUS.

public class Main {
    public static void main(String[] args) {
        // percorra Naipe.values() aqui
    }
}

// crie aqui o enum Naipe
public class Main {
    public static void main(String[] args) {
        for (Naipe n : Naipe.values()) {
            System.out.println(n.ordinal() + " - " + n.name());
        }
    }
}

enum Naipe {
    COPAS, ESPADAS, OUROS, PAUS
}

O compilador protege a lista

A vantagem do enum sobre um texto aparece quando alguém erra. Um método que espera um Semaforo não aceita uma String, e ninguém consegue criar uma quarta cor:

Este código tem um erro de propósito
public class Main {
    static void mostrar(Semaforo s) {
        System.out.println("Sinal: " + s);
    }

    public static void main(String[] args) {
        mostrar("VERDE");
        Semaforo novo = new Semaforo();
    }
}

enum Semaforo {
    VERDE, AMARELO, VERMELHO
}
$ javac Main.java
Main.java:7: error: incompatible types: String cannot be converted to Semaforo
        mostrar("VERDE");
                ^
Main.java:8: error: enum classes may not be instantiated
        Semaforo novo = new Semaforo();
                        ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
2 errors
  • String cannot be converted to Semaforo: o texto "VERDE" não é um Semaforo, mesmo tendo as mesmas letras. Escreva Semaforo.VERDE.
  • Enum classes may not be instantiated = "classes enum não podem ser instanciadas": as constantes da lista são os únicos objetos do tipo; new é proibido.
  • A linha Note: é só um aviso de que o javac resumiu a mensagem; pode ignorá-la.

Com uma String, um mostrar("VERDDE") compilaria e o erro apareceria só lá na frente, se aparecesse. Com o enum, o erro aparece na hora.


De texto para enum: valueOf

Às vezes o valor chega como texto: digitado pelo usuário, lido de um arquivo. O valueOf converte, mas exige o nome exato, com maiúsculas e minúsculas:

public class Main {
    public static void main(String[] args) {
        Semaforo a = Semaforo.valueOf("AMARELO");
        System.out.println("Convertido: " + a);
        Semaforo b = Semaforo.valueOf("verde");
        System.out.println("Convertido: " + b);
    }
}

enum Semaforo {
    VERDE, AMARELO, VERMELHO
}
$ java Main.java
Convertido: AMARELO
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant Semaforo.verde
	at java.base/java.lang.Enum.valueOf(Enum.java:293)
	at Semaforo.valueOf(Main.java:10)
	at Main.main(Main.java:5)

IllegalArgumentException = "exceção de argumento ilegal"; No enum constant Semaforo.verde = "não há constante verde no enum Semaforo". O rastro mostra o caminho: o main (linha 5) chamou o valueOf do Semaforo, que chamou o de Enum, que lançou a exceção. A segunda conversão nunca terminou.

Duas defesas comuns: padronizar o texto antes (Semaforo.valueOf(texto.trim().toUpperCase())) e tratar a exceção com try e catch, para mostrar uma mensagem amigável em vez de derrubar o programa.


Enum no switch

Enums e switch foram feitos um para o outro. Dentro do case, escreve-se só o nome da constante, sem o Semaforo. na frente:

public class Main {
    public static void main(String[] args) {
        Semaforo sinal = Semaforo.AMARELO;
        String acao = switch (sinal) {
            case VERDE -> "Siga";
            case AMARELO -> "Atenção";
            case VERMELHO -> "Pare";
        };
        System.out.println(acao);

        switch (sinal) {
            case VERDE:
                System.out.println("(forma antiga) Siga");
                break;
            case AMARELO:
                System.out.println("(forma antiga) Atenção");
                break;
            default:
                System.out.println("(forma antiga) Pare");
        }
    }
}

enum Semaforo {
    VERDE, AMARELO, VERMELHO
}
$ java Main.java
Atenção
(forma antiga) Atenção

A primeira forma é o switch com setas (Java 14+), usado como expressão que devolve um valor. Repare que ela não tem default: como o compilador conhece todas as constantes, ele sabe que os três case cobrem tudo. E, se faltar uma, ele avisa:

$ javac Main.java
Main.java:4: error: the switch expression does not cover all possible input values
        String acao = switch (sinal) {
                      ^
1 error

"A expressão switch não cobre todos os valores possíveis": faltou o VERMELHO. Isso é ótimo quando o enum cresce: se um dia surgir PISCANDO, todo switch de expressão que esqueceu dele para de compilar, e você é obrigado a decidir o que fazer com a cor nova. Por isso, com enums, prefira não colocar default no switch com setas.


Enums com atributos, construtor e métodos

Um enum é uma classe especial, e pode ter tudo o que uma classe tem. Cada constante pode carregar dados. O semáforo da Ana sabe quanto tempo fica em cada cor e qual é a próxima:

public class Main {
    public static void main(String[] args) {
        Semaforo sinal = Semaforo.VERDE;
        for (int i = 0; i < 4; i++) {
            System.out.println(sinal + " por " + sinal.getSegundos() + " segundos");
            sinal = sinal.proximo();
        }
    }
}

enum Semaforo {
    VERDE(30), AMARELO(5), VERMELHO(40);

    private final int segundos;

    Semaforo(int segundos) {
        this.segundos = segundos;
    }

    int getSegundos() {
        return segundos;
    }

    Semaforo proximo() {
        return switch (this) {
            case VERDE -> AMARELO;
            case AMARELO -> VERMELHO;
            case VERMELHO -> VERDE;
        };
    }
}
VERDE(30)
Cada constante passa argumentos para o construtor, como num new. A lista de constantes termina com ; quando vem mais coisa depois.
private final int segundos;
Um atributo. É final porque as constantes são compartilhadas pelo programa todo: mudá-las seria perigoso.
Semaforo(int segundos)
O construtor. Ele é sempre private (mesmo sem escrever): só as constantes da lista o usam, uma vez cada, quando o enum é carregado.
Semaforo proximo()
Um método comum. Dentro dele, this é a constante em que foi chamado.
$ java Main.java
VERDE por 30 segundos
AMARELO por 5 segundos
VERMELHO por 40 segundos
VERDE por 30 segundos          « depois do vermelho, volta ao verde

Os planetas

Um exemplo clássico (está até na documentação da Oracle): cada planeta com massa e raio, e um método que calcula a gravidade na superfície. A Ana pesa 60 kg na Terra; quanto a balança mostraria nos outros planetas?

public class Main {
    public static void main(String[] args) {
        double pesoNaTerra = 60;
        double massaDaAna = pesoNaTerra / Planeta.TERRA.gravidade();
        for (Planeta p : Planeta.values()) {
            System.out.printf("%s: %.1f kg%n", p.getNome(), p.gravidade() * massaDaAna);
        }
    }
}

enum Planeta {
    MERCURIO("Mercúrio", 3.303e+23, 2.4397e6),
    VENUS("Vênus", 4.869e+24, 6.0518e6),
    TERRA("Terra", 5.976e+24, 6.37814e6),
    MARTE("Marte", 6.421e+23, 3.3972e6),
    JUPITER("Júpiter", 1.9e+27, 7.1492e7);

    private static final double G = 6.67300E-11;

    private final String nome;
    private final double massa;
    private final double raio;

    Planeta(String nome, double massa, double raio) {
        this.nome = nome;
        this.massa = massa;
        this.raio = raio;
    }

    String getNome() {
        return nome;
    }

    double gravidade() {
        return G * massa / (raio * raio);
    }
}
$ java Main.java
Mercúrio: 22,7 kg
Vênus: 54,3 kg
Terra: 60,0 kg
Marte: 22,7 kg          « Mercúrio e Marte têm quase a mesma gravidade
Júpiter: 151,8 kg

Algumas observações: 3.303e+23 é a notação científica do Java (3,303 × 10²³; veja double e float); o printf com %.1f mostra uma casa decimal, com vírgula porque a máquina da Ana está em português (veja Formatar Texto); e a constante G é um static final comum, dentro do enum. O nome com acento ficou num atributo, porque o nome da constante (MERCURIO) é melhor sem acento.

Enums na biblioteca

Você já viu um enum de verdade: LocalDate.getDayOfWeek() devolve um DayOfWeek, com as constantes MONDAY a SUNDAY (veja Datas e Horas). Há também Month, TimeUnit e outros. E existem coleções otimizadas para enums, como EnumMap e EnumSet.


Quando dá errado

IllegalArgumentException: No enum constant …
$ java Main.java
Convertido: AMARELO
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant Semaforo.verde
	at java.base/java.lang.Enum.valueOf(Enum.java:293)
	at Semaforo.valueOf(Main.java:10)
	at Main.main(Main.java:5)

O que aconteceu: o valueOf recebeu um texto que não é o nome exato de nenhuma constante (maiúsculas, espaços e acentos contam).

Como resolver: padronize o texto (.trim().toUpperCase()) e trate a exceção com try/catch quando o texto vier de fora do programa.

"the switch expression does not cover all possible input values"

O que aconteceu: um switch de expressão sobre um enum esqueceu alguma constante.

Como resolver: acrescente o case que falta. Evite resolver com default: assim o compilador continua avisando quando o enum ganhar constantes novas.

"incompatible types: String cannot be converted to …"

O que aconteceu: um texto foi usado onde se espera o enum.

Como resolver: use a constante (Semaforo.VERDE) ou converta o texto com valueOf.

"enum classes may not be instantiated"

O que aconteceu: um new num enum.

Como resolver: as constantes já são os objetos. Se precisa de um valor novo, acrescente-o à lista do enum.


Referência

EscritaO que faz
enum Cor { VERDE, AMARELO }Declara o tipo e as constantes
Cor c = Cor.VERDE;Variável do tipo enum
c == Cor.VERDEComparação (com ==)
Cor.values()Array com todas as constantes
c.ordinal() / c.name()Posição (a partir de 0) / nome como texto
Cor.valueOf("VERDE")Texto → constante; nome inexistente lança IllegalArgumentException
case VERDE -> …No switch, só o nome da constante
VERDE(30), …; + construtorConstantes com dados

Exercícios

1. Declarar

Complete a declaração de um tipo com os tamanhos de camiseta:

Tamanho { P, M, G }

2. Todas as constantes

Complete o laço que percorre todos os tamanhos:

for (Tamanho t : ) { … }

3. A posição

Com enum Tamanho { P, M, G }, quanto vale Tamanho.G.ordinal()?

4. O que Tamanho.valueOf("m") faz, com enum Tamanho { P, M, G }?

5. Num switch sobre um Semaforo, como se escreve o caso do verde?

6. Por que usar um enum em vez de uma String para os dias da semana?

Missão final: a cafeteria da Ana vende café em três tamanhos. Complete o enum Tamanho: o atributo preco e o construtor que o recebe (as constantes já passam os preços). No main, converta o texto pedido em Tamanho com valueOf e mostre GRANDE custa R$ 9.0.

public class Main {
    public static void main(String[] args) {
        String pedido = "GRANDE";
        // converta o pedido com Tamanho.valueOf e mostre o preço
    }
}

enum Tamanho {
    PEQUENO(5.0), MEDIO(7.5), GRANDE(9.0);

    // crie o atributo preco e o construtor

    double getPreco() {
        return preco;
    }
}
public class Main {
    public static void main(String[] args) {
        String pedido = "GRANDE";
        Tamanho t = Tamanho.valueOf(pedido);
        System.out.println(t + " custa R$ " + t.getPreco());
    }
}

enum Tamanho {
    PEQUENO(5.0), MEDIO(7.5), GRANDE(9.0);

    private final double preco;

    Tamanho(double preco) {
        this.preco = preco;
    }

    double getPreco() {
        return preco;
    }
}

Resumo

  • enum é um tipo com uma lista fixa de constantes. O compilador recusa qualquer valor fora dela, e new é proibido.
  • Compare com ==. Todo enum tem values(), ordinal(), name() e valueOf().
  • valueOf exige o nome exato; senão, IllegalArgumentException: No enum constant ….
  • No switch, escreva só o nome da constante. O switch com setas usado como expressão avisa quando falta um caso: prefira-o sem default.
  • Enums podem ter atributos (private final), construtor (sempre privado) e métodos: VERDE(30), TERRA("Terra", …).