Os tipos enumerados (enums), introduzidos no Java 5, são uma funcionalidade robusta que oferece uma maneira elegante de definir um conjunto fixo de constantes. Embora sejam, em essência, um tipo de classe, os enums vêm com restrições específicas que garantem sua concisão, segurança de tipo e facilidade de uso. Vamos explorar o que são enums e como defini-los.
Definição de Tipos Enum
Antes dos enums, era comum definir coleções de constantes usando o padrão "int enum", como ilustrado abaixo:
public class DiasTradicionais {
public static final int SEGUNDA_FEIRA = 1;
public static final int TERCA_FEIRA = 2;
public static final int QUARTA_FEIRA = 3;
public static final int QUINTA_FEIRA = 4;
public static final int SEXTA_FEIRA = 5;
public static final int SABADO = 6;
public static final int DOMINGO = 7;
}
Esse método de definição de constantes não está incorreto, mas apresenta várias desvantagens. Ele não oferece segurança de tipo, o que pode levar a confusões se variáveis com valores inteiros iguais forem misturadas, sem que o compilador emita qualquer aviso. Com a introdução dos enums, essa abordagem se tornou menos recomendada. Agora, podemos redefinir essas constantes de forma mais segura e concisa:
// Enum para os dias da semana
enum DiaDaSemana {
SEGUNDA, TERCA, QUARTA,
QUINTA, SEXTA, SABADO, DOMINGO
}
A sintaxe é notavelmente simples, utilizando a palavra-chave enum, similar ao class, mas para definir um tipo enumerado. As constantes (geralmente em maiúsculas) são separadas por vírgulas. É importante ressaltar que um tipo enum pode ser definido em seu próprio arquivo ou aninhado dentro de outra classe. A principal vantagem é a segurança de tipo e a clareza, com o compilador alertando sobre possíveis problemas de tipo. Lembre-se, um enum é ideal para representar um conjunto finito e predefinido de valores, como os dias da semana.
Para usar um enum, basta referenciar suas constantes diretamente:
public class ExemploEnumSimples {
public static void main(String[] args){
// Atribuição direta de uma constante enum
DiaDaSemana hoje = DiaDaSemana.SEXTA;
System.out.println("Hoje é " + hoje);
}
}
Este é o modelo mais básico de um tipo enum.
Princípios de Implementação de Enums
Ao compilar um tipo enum, o compilador Java gera uma classe que estende a classe abstrata java.lang.Enum do Java API. Isso significa que, por trás das cenas, seu enum é uma classe regular com algumas características especiais. Vamos verificar isso compilando um arquivo com um enum e examinando o bytecode.
Ao compilar ExemploEnumSimples.java, são gerados arquivos .class para ExemploEnumSimples e DiaDaSemana. O arquivo DiaDaSemana.class é, de fato, uma classe gerada pelo compilador. Uma análise do bytecode revelaria que:
- A classe
DiaDaSemanaé declarada comofinal, impedindo herança. - Ela estende
java.lang.Enum. - Cada constante do enum (e.g.,
SEGUNDA,TERCA) é um objetopublic static finaldo tipoDiaDaSemana. - Um construtor privado é gerado.
- Métodos estáticos
values()evalueOf(String)são adicionados, permitindo acesso e busca pelas constantes do enum.
Essa compreensão do mecanismo interno é crucial: um enum não é apenas um conjunto de constantes, mas uma classe completa com instâncias pré-definidas e métodos auxiliares.
Métodos Comuns de Enums
A classe java.lang.Enum é a base para todos os tipos enumerados em Java. Abaixo estão alguns de seus métodos principais:
| Tipo de Retorno | Nome do Método | Descrição |
|---|---|---|
int |
compareTo(E o) |
Compara a ordem desta constante enum com a de outro objeto. |
boolean |
equals(Object other) |
Retorna true se o objeto especificado for igual a esta constante enum. |
Class<?> |
getDeclaringClass() |
Retorna o objeto Class que representa o tipo enum desta constante. |
String |
name() |
Retorna o nome exato desta constante enum, como declarado. |
int |
ordinal() |
Retorna a posição ordinal desta constante enum (sua posição na declaração, começando em zero). |
String |
toString() |
Retorna o nome desta constante enum (geralmente idêntico a name(), mas pode ser sobrescrito). |
static <T extends Enum<T>> T |
valueOf(Class<T> enumType, String name) |
Retorna a constante enum do tipo especificado com o nome fornecido. |
O método ordinal() é particularmente notável, pois retorna a ordem de declaração da constante enum, começando do zero. No entanto, seu uso deve ser evitado na maioria das aplicações, pois essa ordem pode mudar com o tempo. compareTo(E o) realiza a comparação com base nos valores ordinais. name() e toString() geralmente fornecem o mesmo resultado, que é o nome da constante como uma string.
Considere o seguinte exemplo para um enum de níveis de prioridade:
import java.util.Arrays;
enum NivelPrioridade {
BAIXA, MEDIA, ALTA, URGENTE
}
public class ExemploMetodosEnum {
public static void main(String[] args) {
NivelPrioridade[] prioridades = NivelPrioridade.values();
System.out.println("---- Ordinal ----");
for (int i = 0; i < prioridades.length; i++) {
System.out.println("prioridade[" + i + "].ordinal(): " + prioridades[i].ordinal());
}
System.out.println("\n---- CompareTo ----");
System.out.println("BAIXA vs MEDIA: " + NivelPrioridade.BAIXA.compareTo(NivelPrioridade.MEDIA)); // -1
System.out.println("ALTA vs BAIXA: " + NivelPrioridade.ALTA.compareTo(NivelPrioridade.BAIXA)); // 2
System.out.println("\n---- Name & ToString ----");
System.out.println("prioridades[2].name(): " + prioridades[2].name()); // ALTA
System.out.println("prioridades[2].toString(): " + prioridades[2].toString()); // ALTA
System.out.println("\n---- GetDeclaringClass ----");
Class<?> clazz = prioridades[0].getDeclaringClass();
System.out.println("Classe declaradora: " + clazz.getName()); // NivelPrioridade
System.out.println("\n---- ValueOf (java.lang.Enum) ----");
NivelPrioridade p = Enum.valueOf(NivelPrioridade.class, "URGENTE");
System.out.println("Obtido por valueOf: " + p); // URGENTE
}
}
Métodos Gerados pelo Compilador: values() e valueOf(String)
Os métodos values() e valueOf(String name) são estáticos e são inseridos automaticamente pelo compilador na sua classe enum. O método values() retorna um array contendo todas as constantes do enum na ordem em que foram declaradas. O valueOf(String name) é uma versão simplificada do java.lang.Enum.valueOf(), que permite obter uma constante enum a partir do seu nome (string).
// Usando os métodos gerados pelo compilador
NivelPrioridade[] todasPrioridades = NivelPrioridade.values();
System.out.println("Todas as prioridades: " + Arrays.toString(todasPrioridades));
NivelPrioridade prioridadeMedia = NivelPrioridade.valueOf("MEDIA");
System.out.println("Prioridade obtida por nome: " + prioridadeMedia);
É crucial notar que, como values() e valueOf(String) são métodos estáticos gerados pelo compilador na classe do enum (e não na classe java.lang.Enum), eles não podem ser chamados em uma referência polimórfica para java.lang.Enum. Por exemplo:
NivelPrioridade pBaixa = NivelPrioridade.BAIXA; // Uso normal
// Enum generico = pBaixa; // Upcast para java.lang.Enum
// generico.values(); // Erro de compilação! O método 'values()' não existe em java.lang.Enum
Enums e Objetos Class
Mesmo que o método values() não possa ser invocado através de uma referência java.lang.Enum, é possível obter todas as constantes de um enum usando a reflexão através do objeto Class do enum. A classe Class fornece os seguintes métodos úteis para enums:
| Tipo de Retorno | Nome do Método | Descrição |
|---|---|---|
T[] |
getEnumConstants() |
Retorna um array contendo os elementos deste tipo enum, ou null se o objeto Class não representar um tipo enum. |
boolean |
isEnum() |
Retorna true se e somente se esta classe for declarada como um enum no código-fonte. |
Com getEnumConstants(), podemos facilmente recuperar todas as instâncias de um enum:
// Obtendo o objeto Class de uma constante enum
Class<?> classeEnum = NivelPrioridade.URGENTE.getDeclaringClass();
if (classeEnum.isEnum()) {
NivelPrioridade[] constantes = (NivelPrioridade[]) classeEnum.getEnumConstants();
System.out.println("Constantes via getEnumConstants(): " + Arrays.toString(constantes));
}
Uso Avançado de Enums
Adicionando Métodos e Construtores Personalizados
Um tipo enum pode ser tratado como uma classe comum (com a exceção de que não pode ser estendido, pois já estende java.lang.Enum). Isso significa que você pode adicionar campos, métodos e até um método main a um enum. Considere um enum para as estações do ano, cada uma com uma descrição em português:
public enum EstacaoDoAno {
PRIMAVERA("A estação das flores"),
VERAO("A estação mais quente"),
OUTONO("A estação das folhas caindo"),
INVERNO("A estação mais fria"); // Ponto e vírgula é obrigatório se houver métodos ou campos
private String descricao;
// Construtor privado para evitar instâncias externas
private EstacaoDoAno(String descricao) {
this.descricao = descricao;
}
// Método público para acessar a descrição
public String getDescricao() {
return descricao;
}
public static void main(String[] args) {
for (EstacaoDoAno estacao : EstacaoDoAno.values()) {
System.out.println("Estação: " + estacao.name() + ", Descrição: " + estacao.getDescricao());
}
}
}
Observe que o ponto e vírgula é obrigatório após a última constante do enum se houver declarações de métodos ou campos subsequentes. O construtor é sempre privado ou implicitamente privado, e você nunca poderá invocá-lo diretamente para criar uma nova instância de enum, pois isso é gerenciado pelo compilador.
Sobrescrevendo Métodos da Classe Enum
Apenas o método toString() da classe java.lang.Enum não é declarado como final, o que significa que ele pode ser sobrescrito para fornecer uma representação de string personalizada para cada constante do enum. Isso pode, por exemplo, eliminar a necessidade de um método getDescricao():
public enum EstacaoDoAno {
PRIMAVERA("A estação das flores"),
VERAO("A estação mais quente"),
OUTONO("A estação das folhas caindo"),
INVERNO("A estação mais fria");
private String descricao;
private EstacaoDoAno(String descricao) {
this.descricao = descricao;
}
@Override
public String toString() {
return descricao; // Retorna a descrição em vez do nome da constante
}
public static void main(String[] args) {
for (EstacaoDoAno estacao : EstacaoDoAno.values()) {
System.out.println("Estação: " + estacao.name() + ", Representação: " + estacao.toString());
}
}
}
Métodos Abstratos em Enums
É possível definir métodos abstratos dentro de um enum, exigindo que cada constante enum forneça sua própria implementação. Isso permite que cada constante exiba um comportamento diferente. O uso da palavra-chave abstract para o próprio enum não é estritamente necessário.
public enum TipoMensagem {
EMAIL {
@Override
public void enviar(String destinatario, String conteudo) {
System.out.println("Enviando EMAIL para " + destinatario + ": " + conteudo);
}
},
SMS {
@Override
public void enviar(String destinatario, String conteudo) {
System.out.println("Enviando SMS para " + destinatario + ": " + conteudo);
}
},
PUSH {
@Override
public void enviar(String destinatario, String conteudo) {
System.out.println("Enviando PUSH para " + destinatario + ": " + conteudo);
}
}; // Ponto e vírgula obrigatório
public abstract void enviar(String destinatario, String conteudo);
public static void main(String[] args) {
TipoMensagem.EMAIL.enviar("usuario@example.com", "Sua fatura está disponível.");
TipoMensagem.SMS.enviar("5511987654321", "Código de verificação: 12345");
TipoMensagem.PUSH.enviar("dispositivo_id_xyz", "Nova notificação no aplicativo!");
}
}
Essa abordagem permite que as constantes do enum exibam um comportamento polimórfico, embora as instâncias de enum não possam ser passadas como tipos, ou seja, public void processar(TipoMensagem.EMAIL tipo) não é válido.
Enums e Interfaces
Enums podem implementar interfaces, superando a limitação de herança única do Java. Isso é útil para categorizar e agrupar conjuntos relacionados de constantes.
interface AcaoInterativa {
void interagir();
}
interface AcaoFisica {
void mover();
}
public enum ComportamentoPadrao implements AcaoInterativa, AcaoFisica {
INTERAGIR_PADRAO {
@Override
public void interagir() { System.out.println("Interagindo por padrão..."); }
@Override
public void mover() { System.out.println("Movendo-se para interagir..."); }
},
EXERCITAR_PADRAO {
@Override
public void interagir() { System.out.println("Interagindo enquanto exercita..."); }
@Override
public void mover() { System.out.println("Movendo-se para exercitar..."); }
}; // Ponto e vírgula
// Métodos para o enum principal, que podem ser implementados ou deixar para as constantes
// ...
}
Uma aplicação comum é a organização de dados hierárquicos, como um menu de restaurante, onde cada categoria (entrada, prato principal, etc.) é um tipo enum aninhado que implementa uma interface comum:
public interface ItemDeMenu {
// Enums aninhados para categorizar itens de menu
enum Entrada implements ItemDeMenu {
SALADA, SOPA, ROLINHO_PRIMAVERA;
}
enum PratoPrincipal implements ItemDeMenu {
LASANHA, BURRITO, PAD_THAI,
LENTILHAS, HUMMUS, VINDALOO;
}
enum Sobremesa implements ItemDeMenu {
TIRAMISU, GELATO, BOLO_FLORESTA_NEGRA,
FRUTAS, CREME_CARAMELO;
}
enum Bebida implements ItemDeMenu {
CAFE_PRETO, CAFE_DESCAFEINADO, ESPRESSO,
LATTE, CAPPUCCINO, CHA, CHA_DE_ERVAS;
}
}
public class ExemploMenu {
public static void main(String[] args) {
ItemDeMenu item1 = ItemDeMenu.Entrada.SALADA;
ItemDeMenu item2 = ItemDeMenu.PratoPrincipal.LASANHA;
ItemDeMenu item3 = ItemDeMenu.Sobremesa.GELATO;
ItemDeMenu item4 = ItemDeMenu.Bebida.CAPPUCCINO;
System.out.println("Item selecionado: " + item1);
}
}
Essa estrutura permite um gerenciamento unificado dos itens do menu. Podemos expandir isso criando um enum TipoDeRefeicao que agrega essas categorias:
public enum TipoDeRefeicao {
ENTRADA(ItemDeMenu.Entrada.class),
PRATO_PRINCIPAL(ItemDeMenu.PratoPrincipal.class),
SOBREMESA(ItemDeMenu.Sobremesa.class),
BEBIDA(ItemDeMenu.Bebida.class);
private ItemDeMenu[] itensDisponiveis;
private TipoDeRefeicao(Class<? extends ItemDeMenu> tipoItens) {
// Obtém as constantes enum da classe aninhada
itensDisponiveis = tipoItens.getEnumConstants();
}
// A interface ItemDeMenu com seus enums aninhados (conforme exemplo anterior)
public interface ItemDeMenu {
enum Entrada implements ItemDeMenu { SALADA, SOPA, ROLINHO_PRIMAVERA; }
enum PratoPrincipal implements ItemDeMenu { LASANHA, BURRITO, PAD_THAI; }
enum Sobremesa implements ItemDeMenu { TIRAMISU, GELATO, FRUTAS; }
enum Bebida implements ItemDeMenu { CAFE_PRETO, ESPRESSO, LATTE; }
}
}
Enums e Declarações Switch
Os enums são suportados nativamente nas declarações switch, assim como inteiros e caracteres. Isso melhora a legibilidade e a segurança de tipo do código. Em Java 7 e versões posteriores, o switch também suporta Strings.
enum StatusPedido { NOVO, PROCESSANDO, ENVIADO, ENTREGUE, CANCELADO }
public class ExemploSwitchEnum {
public static void exibirStatus(StatusPedido status) {
switch (status) {
case NOVO: // Não é necessário usar StatusPedido.NOVO
System.out.println("Pedido em status: Novo");
break;
case PROCESSANDO:
System.out.println("Pedido em status: Processando");
break;
case ENVIADO:
System.out.println("Pedido em status: Enviado");
break;
case ENTREGUE:
System.out.println("Pedido em status: Entregue");
break;
case CANCELADO:
System.out.println("Pedido em status: Cancelado");
break;
default:
System.out.println("Status desconhecido.");
}
}
public static void main(String[] args) {
exibirStatus(StatusPedido.PROCESSANDO);
exibirStatus(StatusPedido.ENTREGUE);
exibirStatus(StatusPedido.NOVO);
}
}
Observe que, dentro do switch, você não precisa prefixar as constantes do enum com o nome do tipo enum (e.g., case NOVO: em vez de case StatusPedido.NOVO:).
Enums e o Padrão Singleton
O padrão Singleton garante que uma classe tenha apenas uma instância e forneça um ponto de acesso global a ela. É frequentemente usado para gerenciadores de pool de threads, objetos de log ou configurações. Vamos comparar as implementações tradicionais com a abordagem enum.
Implementações Tradicionais do Singleton
-
Singleton Eager (Ansioso): A instância é criada na inicialização da classe.
public class ConfiguracaoEstatica { private static final ConfiguracaoEstatica INSTANCIA = new ConfiguracaoEstatica(); private ConfiguracaoEstatica() { // Construtor privado } public static ConfiguracaoEstatica obterInstancia() { return INSTANCIA; } }Simples, mas não oferece carregamento preguiçoso (lazy loading).
-
Singleton Lazy (Preguiçoso) com Sincronização: A instância é criada apenas quando solicitada pela primeira vez.
public class GerenciadorLento { private static GerenciadorLento instancia; private GerenciadorLento() { // Construtor privado } public static synchronized GerenciadorLento obterInstancia() { if (instancia == null) { instancia = new GerenciadorLento(); } return instancia; } }Oferece lazy loading e é thread-safe, mas o
synchronizedno métodoobterInstancia()pode ser um gargalo de desempenho em ambientes multi-thread. -
Double-Checked Locking (DCL): Uma melhoria para o Singleton Lazy, visando melhor desempenho em multi-threads.
public class ConexaoSegura { private static volatile ConexaoSegura instancia; // 'volatile' é crucial private ConexaoSegura() { // Construtor privado } public static ConexaoSegura obterInstancia() { if (instancia == null) { // Primeira verificação synchronized (ConexaoSegura.class) { if (instancia == null) { // Segunda verificação instancia = new ConexaoSegura(); } } } return instancia; } }O
volatilegarante visibilidade e impede reordenação de instruções, essencial para a segurança do DCL em Java 5+. Embora mais eficiente, ainda é mais complexo. -
Singleton com Classe Interna Estática: Uma solução elegante que combina lazy loading e segurança de threads.
public class CacheEstatico { private CacheEstatico() { // Construtor privado } private static class DetentorInstancia { private static final CacheEstatico INSTANCIA = new CacheEstatico(); } public static CacheEstatico obterInstancia() { return DetentorInstancia.INSTANCIA; } }Nessa abordagem, a instância do Singleton é criada somente quando o método
obterInstancia()é chamado pela primeira vez, devido ao carregamento preguiçoso da classe interna estática.
Problemas das Implementações Tradicionais
As implementações acima podem ser vulneráveis a:
-
Serialização: A desserialização pode criar novas instâncias. A solução envolve implementar
readResolve():import java.io.Serializable; public class ConfiguracaoEstatica implements Serializable { public static ConfiguracaoEstatica INSTANCIA = new ConfiguracaoEstatica(); protected ConfiguracaoEstatica() {} // Este método é invocado durante a desserialização para garantir uma única instância private Object readResolve() { return INSTANCIA; } } -
Reflexão: É possível invocar o construtor privado via reflexão. A solução é modificar o construtor para lançar uma exceção se uma segunda instância for tentada:
public class ConfiguracaoEstatica { public static ConfiguracaoEstatica INSTANCIA = new ConfiguracaoEstatica(); private static volatile boolean primeiraInstancia = true; private ConfiguracaoEstatica() { if (primeiraInstancia) { primeiraInstancia = false; } else { throw new RuntimeException("Uma instância de ConfiguracaoEstatica já existe!"); } } }
Essas soluções adicionam complexidade ao código.
Singleton com Enum: A Solução Ideal
A forma mais simples e robusta de implementar o padrão Singleton é usando um enum. Esta abordagem é imune aos problemas de serialização e reflexão.
public enum ConfiguracaoSistema {
INSTANCIA;
private String parametro;
public String getParametro() {
return parametro;
}
public void setParametro(String parametro) {
this.parametro = parametro;
}
public void exibirConfiguracao() {
System.out.println("Configuração atual: " + parametro);
}
}
Para acessar a instância, basta usar ConfiguracaoSistema.INSTANCIA, eliminando a necessidade de um método getInstance(). A segurança é garantida pela JVM:
- Serialização: A JVM lida com a serialização e desserialização de enums de forma especial, garantindo que nenhuma nova instância seja criada durante a desserialização. Ela serializa apenas o nome da constante e, na desserialização, usa
java.lang.Enum.valueOf()para encontrar a instância existente. - Reflexão: A JVM impede a criação de instâncias de enum via reflexão. O método
Constructor.newInstance()da API de Reflexão lança umaIllegalArgumentExceptionse você tentar instanciar um tipo enum.
Exemplo de tentativa de criação via reflexão (que falha):
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class TesteEnumSingletonReflexao {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// Tenta obter o construtor de enum (que recebe String name e int ordinal)
Constructor<ConfiguracaoSistema> construtor = ConfiguracaoSistema.class.getDeclaredConstructor(String.class, int.class);
construtor.setAccessible(true);
// Tenta criar uma nova instância, o que irá falhar
ConfiguracaoSistema outraInstancia = construtor.newInstance("OUTRA_INSTANCIA", 1);
System.out.println(outraInstancia.getParametro()); // Este código nunca será alcançado
}
}
Esta tentativa resultará em uma IllegalArgumentException com a mensagem "Cannot reflectively create enum objects".
Apesar de suas vantagens, é importante notar que em plataformas como Android, o uso de enums pode ter implicações na memória (ocupando mais espaço do que constantes estáticas). Por isso, o Google recomenda cautela ao usá-los em desenvolvimento Android, a menos que os benefícios superem o custo.
EnumMap
EnumMap é uma implementação de Map especializada para ser usada com chaves de tipos enum. É altamente eficiente devido à sua estrutura interna otimizada para enums.
Uso Básico do EnumMap
Imagine que você precisa contar a quantidade de diferentes tipos de produtos em um estoque. Definimos um enum TipoProduto:
enum TipoProduto {
ELETRONICO, VESTUARIO, ALIMENTO, LIVRO, BRINQUEDO
}
class ItemEstoque {
String id;
TipoProduto tipo;
public ItemEstoque(String id, TipoProduto tipo) {
this.id = id;
this.tipo = tipo;
}
public TipoProduto getTipo() {
return tipo;
}
}
Agora, vamos contar os itens usando HashMap e EnumMap:
import java.util.*;
public class ExemploContadorProdutos {
public static void main(String[] args) {
List<ItemEstoque> estoque = new ArrayList<>();
estoque.add(new ItemEstoque("E001", TipoProduto.ELETRONICO));
estoque.add(new ItemEstoque("V001", TipoProduto.VESTUARIO));
estoque.add(new ItemEstoque("A001", TipoProduto.ALIMENTO));
estoque.add(new ItemEstoque("L001", TipoProduto.LIVRO));
estoque.add(new ItemEstoque("E002", TipoProduto.ELETRONICO));
estoque.add(new ItemEstoque("V002", TipoProduto.VESTUARIO));
estoque.add(new ItemEstoque("B001", TipoProduto.BRINQUEDO));
estoque.add(new ItemEstoque("E003", TipoProduto.ELETRONICO));
// Contagem com HashMap
Map<String, Integer> contagemHashMap = new HashMap<>();
for (ItemEstoque item : estoque) {
String nomeTipo = item.getTipo().name();
contagemHashMap.put(nomeTipo, contagemHashMap.getOrDefault(nomeTipo, 0) + 1);
}
System.out.println("Contagem (HashMap): " + contagemHashMap);
System.out.println("-------------------------------------");
// Contagem com EnumMap
Map<TipoProduto, Integer> contagemEnumMap = new EnumMap<>(TipoProduto.class);
for (ItemEstoque item : estoque) {
TipoProduto tipo = item.getTipo();
contagemEnumMap.put(tipo, contagemEnumMap.getOrDefault(tipo, 0) + 1);
}
System.out.println("Contagem (EnumMap): " + contagemEnumMap);
}
}
Ambas as soluções funcionam, mas EnumMap é mais direto e eficiente quando a chave é um enum, pois elimina a necessidade de converter o enum para String e se beneficia de uma implementação interna otimizada. A chave de um EnumMap não pode ser null.
Princípios de Implementação do EnumMap
EnumMap internamente utiliza dois arrays para armazenar seus dados: um para as chaves (as constantes do enum) e outro para os valores. O tamanho desses arrays é determinado pelo número de constantes do tipo enum. A classe EnumMap herda de AbstractMap e implementa Serializable e Cloneable.
Seus construtores:
EnumMap(Class<K> keyType): Cria um mapa vazio para um tipo de chave enum especificado.EnumMap(EnumMap<K,? extends V> m): Cria um mapa com as mesmas chaves e valores de outroEnumMap.EnumMap(Map<K,? extends V> m): Cria um mapa inicializado com as chaves e valores de qualquer outroMap.
A eficiência do EnumMap deriva do fato de que cada constante enum possui um valor ordinal único (sua posição na declaração, começando em 0). Esse ordinal é usado diretamente como índice nos arrays internos para armazenamento e recuperação de valores. Por exemplo, no método put():
public V put(K key, V value) {
// typeCheck(key); // Valida que a chave é do tipo enum correto
int index = key.ordinal(); // Obtém o índice baseado no ordinal
Object oldValue = vals[index];
vals[index] = maskNull(value); // Armazena o valor (com tratamento para null)
if (oldValue == null) size++;
return unmaskNull(oldValue); // Retorna o valor antigo (com tratamento para null)
}
Os métodos maskNull() e unmaskNull() tratam valores null de forma especial, usando um objeto interno NULL para representá-los nos arrays, garantindo que o array de valores possa armazenar nulls sem problemas. A recuperação de valores (método get()) é igualmente eficiente, acessando diretamente o array pelo ordinal:
public V get(Object key) {
return (isValidKey(key) ?
unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
}
Essa abordagem baseada em array garante que EnumMap mantenha a ordem de inserção das chaves conforme a ordem de declaração do enum e oferece desempenho de acesso O(1), similar a um array, mas com a interface de um Map.
EnumSet
EnumSet é uma implementação de Set otimizada para uso exclusivo com tipos enumerados. Todos os elementos em um EnumSet devem ser do mesmo tipo enum. Ao contrário de HashSet ou TreeSet, EnumSet é implementado internamente usando vetores de bits (bit vectors), o que resulta em uma performance de tempo e espaço excepcionalmente alta. Isso o torna tão eficiente quanto o uso de "bit flags" tradicionais, mas com os benefícios de segurança de tipo e legibilidade de um Set.
EnumSet não permite elementos null; tentar adicionar um resultará em NullPointerException. Assim como a maioria das coleções, EnumSet não é thread-safe e requer sincronização externa em ambientes multi-thread.
Uso de EnumSet
Você não pode instanciar EnumSet diretamente usando new, pois é uma classe abstrata. Em vez disso, utilize seus métodos de fábrica estáticos:
noneOf(Class<E> elementType): Cria umEnumSetvazio do tipo especificado.allOf(Class<E> elementType): Cria umEnumSetcontendo todas as constantes do tipo enum.range(E from, E to): Cria umEnumSetcom elementos dentro de um intervalo especificado.complementOf(EnumSet<E> s): Cria umEnumSetcom todos os elementos do tipo enum que não estão noEnumSetdado.of(E e),of(E e1, E e2, ...),of(E first, E... rest): Cria umEnumSetcom os elementos especificados.copyOf(EnumSet<E> s),copyOf(Collection<E> c): Cria umEnumSeta partir de outroEnumSetou uma coleção.
Exemplo prático:
import java.util.Collection;
import java.util.EnumSet;
import java.util.ArrayList;
enum PermissaoUsuario {
LER, ESCREVER, EXECUTAR, DELETAR, COMPARTILHAR
}
public class ExemploPermissoes {
public static void main(String[] args) {
// 1. Criar um EnumSet vazio
EnumSet<PermissaoUsuario> permissoesVazias = EnumSet.noneOf(PermissaoUsuario.class);
System.out.println("Permissões vazias: " + permissoesVazias);
permissoesVazias.add(PermissaoUsuario.LER);
permissoesVazias.add(PermissaoUsuario.ESCREVER);
System.out.println("Permissões após adicionar: " + permissoesVazias);
System.out.println("-----------------------------------");
// 2. Criar um EnumSet com todas as constantes
EnumSet<PermissaoUsuario> todasPermissoes = EnumSet.allOf(PermissaoUsuario.class);
System.out.println("Todas as permissões: " + todasPermissoes);
System.out.println("-----------------------------------");
// 3. Criar um EnumSet com um intervalo de constantes
EnumSet<PermissaoUsuario> permissoesBasicas = EnumSet.range(PermissaoUsuario.LER, PermissaoUsuario.EXECUTAR);
System.out.println("Permissões básicas (LER a EXECUTAR): " + permissoesBasicas);
System.out.println("-----------------------------------");
// 4. Criar um EnumSet como o complemento de outro
EnumSet<PermissaoUsuario> permissoesAvancadas = EnumSet.complementOf(permissoesBasicas);
System.out.println("Permissões avançadas (complemento de básicas): " + permissoesAvancadas);
System.out.println("-----------------------------------");
// 5. Criar um EnumSet com elementos específicos
EnumSet<PermissaoUsuario> permissoesSelecao = EnumSet.of(PermissaoUsuario.DELETAR, PermissaoUsuario.COMPARTILHAR);
System.out.println("Permissões selecionadas: " + permissoesSelecao);
System.out.println("-----------------------------------");
// 6. Criar um EnumSet copiando de outro EnumSet
EnumSet<PermissaoUsuario> copiaPermissoes = EnumSet.copyOf(permissoesSelecao);
System.out.println("Cópia de permissões: " + copiaPermissoes);
System.out.println("-----------------------------------");
// 7. Criar um EnumSet copiando de uma Collection
Collection<PermissaoUsuario> listaTemp = new ArrayList<>();
listaTemp.add(PermissaoUsuario.LER);
listaTemp.add(PermissaoUsuario.DELETAR);
listaTemp.add(PermissaoUsuario.LER); // Duplicado, Set não adicionará
EnumSet<PermissaoUsuario> permissoesDaLista = EnumSet.copyOf(listaTemp);
System.out.println("Permissões da lista: " + permissoesDaLista);
}
}