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.
- Como declarar e usar um
enumsimples. - Os métodos que todo enum tem:
values(),ordinal(),name()evalueOf()(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 comenumno lugar declass. - 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 (ounull). - 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étodo | O que devolve | Exemplo → resultado |
|---|---|---|
Tipo.values() | Um array com todas as constantes, na ordem da declaração | Semaforo.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 texto | Semaforo.AMARELO.name() → "AMARELO" |
constante.toString() | Igual ao name(), a menos que você sobrescreva | usado pelo println |
Tipo.valueOf("NOME") | A constante com aquele nome | Semaforo.valueOf("VERDE") → VERDE |
a.compareTo(b) | Compara pela ordem da declaração | VERDE.compareTo(VERMELHO) → negativo |
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:
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 é umSemaforo, mesmo tendo as mesmas letras. EscrevaSemaforo.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 ojavacresumiu 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. É
finalporque 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.
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.
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.
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.
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
| Escrita | O que faz |
|---|---|
enum Cor { VERDE, AMARELO } | Declara o tipo e as constantes |
Cor c = Cor.VERDE; | Variável do tipo enum |
c == Cor.VERDE | Comparaçã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), …; + construtor | Constantes com dados |
Exercícios
1. Declarar
Complete a declaração de um tipo com os tamanhos de camiseta:
2. Todas as constantes
Complete o laço que percorre todos os tamanhos:
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, enewé proibido.- Compare com
==. Todo enum temvalues(),ordinal(),name()evalueOf(). valueOfexige o nome exato; senão,IllegalArgumentException: No enum constant ….- No
switch, escreva só o nome da constante. Oswitchcom setas usado como expressão avisa quando falta um caso: prefira-o semdefault. - Enums podem ter atributos (
private final), construtor (sempre privado) e métodos:VERDE(30),TERRA("Terra", …).