Enumeraciones (enum) en Java
En esta lección
Muchos datos solo pueden tomar unos pocos valores fijos: los días de la semana, las tallas de una camiseta, los estados de un pedido. Java tiene un tipo pensado exactamente para eso: el enum (enumeración). En esta lección aprenderás a definirlos, a usarlos en un switch, a recorrerlos y a darles atributos y métodos propios, porque en Java un enum es mucho más que una lista de nombres.
Construiremos la parte de pedidos de una tienda online: los estados por los que pasa un pedido y los tipos de envío.
El problema de las constantes int y String
Antes de que existieran los enums, los estados se representaban con constantes numéricas:
public static final int PENDIENTE = 0;
public static final int PAGADO = 1;
public static final int ENVIADO = 2;
public static void cambiarEstado(int estado) { ... }
Esto tiene varios problemas:
- Nada impide valores sin sentido.
cambiarEstado(57)compila y se ejecuta sin quejarse. - No es legible. Si imprimes el estado, ves
2, noENVIADO. - Se pueden mezclar cosas distintas. Si también tienes
TALLA_M = 2, el compilador no distingue una talla de un estado.
Con String ("enviado") es parecido: una errata como "envaido" compila perfectamente y el fallo aparece mucho después. Un enum resuelve todo esto.
Definir y usar un enum
public enum EstadoPedido {
PENDIENTE, PAGADO, ENVIADO, ENTREGADO, CANCELADO
}
- Se declara con
enumen lugar declass, normalmente en su propio archivo (EstadoPedido.java). - Los valores se llaman constantes del enum y se escriben en MAYÚSCULAS, separados por comas.
- El nombre del enum va en singular y en PascalCase, como una clase.
Para usarlo, escribe el nombre del enum, un punto y la constante:
EstadoPedido estado = EstadoPedido.PENDIENTE;
System.out.println(estado); // PENDIENTE
if (estado == EstadoPedido.PENDIENTE) {
System.out.println("Aún no se ha pagado"); // Aún no se ha pagado
}
Fíjate en tres cosas:
- Una variable de tipo
EstadoPedidosolo puede contener uno de esos cinco valores (onull).cambiarEstado(57)ya no compila. - Al imprimirlo se ve su nombre, no un número.
- Los enums se comparan con
==. Cada constante existe una sola vez en todo el programa, así que==es seguro y además no falla si la variable esnull.
Tampoco se pueden crear valores nuevos: new EstadoPedido() da el error enum classes may not be instantiated.
Enums en switch
Los enums y el switch encajan de maravilla. Dentro de los case se escribe la constante sin el nombre del enum:
public static String mensaje(EstadoPedido estado) {
switch (estado) {
case PENDIENTE:
return "Esperando el pago";
case PAGADO:
return "Preparando tu pedido";
case ENVIADO:
return "Tu pedido está en camino";
case ENTREGADO:
return "Entregado. ¡Que lo disfrutes!";
default:
return "Pedido cancelado";
}
}
Con la sintaxis moderna de switch con flechas (la tienes en condicionales) queda más corto:
public static String mensaje(EstadoPedido estado) {
return switch (estado) {
case PENDIENTE -> "Esperando el pago";
case PAGADO -> "Preparando tu pedido";
case ENVIADO -> "Tu pedido está en camino";
case ENTREGADO -> "Entregado. ¡Que lo disfrutes!";
case CANCELADO -> "Pedido cancelado";
};
}
System.out.println(mensaje(EstadoPedido.ENVIADO)); // Tu pedido está en camino
Aquí no hace falta default: el compilador sabe que el enum tiene exactamente cinco valores y comprueba que están todos. Si falta alguno, da el error the switch expression does not cover all possible input values. Es una gran ventaja: si mañana añades el estado DEVUELTO, Java te señalará cada switch que tienes que actualizar.
Métodos que traen todos los enums
Todo enum hereda automáticamente de la clase java.lang.Enum y trae varios métodos útiles.
values(): recorrer todas las constantes
Devuelve un array con todas las constantes, en el orden en que se declararon:
for (EstadoPedido e : EstadoPedido.values()) {
System.out.println(e.ordinal() + " " + e.name());
}
// 0 PENDIENTE
// 1 PAGADO
// 2 ENVIADO
// 3 ENTREGADO
// 4 CANCELADO
System.out.println(EstadoPedido.values().length); // 5
Es perfecto para rellenar un desplegable o mostrar un menú con todas las opciones.
name() y ordinal()
name()devuelve el nombre de la constante comoString:"ENVIADO". Por defectotoString()devuelve lo mismo.ordinal()devuelve su posición en la declaración, empezando por 0.
Cuidado: no guardes el
ordinal()en una base de datos ni en un fichero. Si algún día añades una constante en medio, todos los números cambian y los datos antiguos pasan a significar otra cosa. Guarda elname().
valueOf(): de texto a enum
valueOf hace lo contrario que name(): recibe un texto y devuelve la constante correspondiente. Es muy útil cuando el dato llega de un formulario, de un fichero o del teclado:
EstadoPedido p = EstadoPedido.valueOf("PAGADO");
System.out.println(p); // PAGADO
El texto tiene que coincidir exactamente, mayúsculas incluidas. Si no, lanza una excepción:
EstadoPedido.valueOf("pagado");
// IllegalArgumentException: No enum constant EstadoPedido.pagado
EstadoPedido.valueOf("pagado".toUpperCase()); // PAGADO
compareTo()
Los enums implementan Comparable (lo viste en clases abstractas e interfaces) y se ordenan según su posición:
System.out.println(EstadoPedido.ENVIADO.compareTo(EstadoPedido.PAGADO)); // 1
Un número positivo indica que ENVIADO va después de PAGADO.
Enums con atributos, constructor y métodos
Aquí está la verdadera potencia de los enums en Java: son clases. Cada constante es un objeto y puede llevar sus propios datos.
La tienda ofrece tres tipos de envío, cada uno con una descripción, un precio y unos días de entrega:
public enum Envio {
ESTANDAR("Envío estándar", 3.99, 5),
URGENTE("Envío urgente", 7.50, 1),
RECOGIDA("Recogida en tienda", 0, 2); // punto y coma: acaban las constantes
private final String descripcion;
private final double precio;
private final int dias;
Envio(String descripcion, double precio, int dias) {
this.descripcion = descripcion;
this.precio = precio;
this.dias = dias;
}
public String getDescripcion() { return descripcion; }
public double getPrecio() { return precio; }
public int getDias() { return dias; }
public boolean esGratis() {
return precio == 0;
}
}
Cómo funciona:
- Cada constante lleva entre paréntesis los argumentos para el constructor.
- Tras la última constante va un punto y coma y luego el resto de la clase.
- El constructor es privado siempre (no hace falta escribir
private): solo Java lo llama, una vez por constante, al cargar el enum. - Los atributos suelen ser
final, porque las constantes no deberían cambiar. Repasa por qué en encapsulación.
Envio envio = Envio.URGENTE;
System.out.println(envio.getDescripcion() + ": " + envio.getPrecio()
+ " € en " + envio.getDias() + " día(s)");
// Envío urgente: 7.5 € en 1 día(s)
for (Envio e : Envio.values()) {
System.out.println(e + " gratis? " + e.esGratis());
}
// ESTANDAR gratis? false
// URGENTE gratis? false
// RECOGIDA gratis? true
Toda la información de cada envío está en un único sitio. Si cambia el precio del urgente, se toca una línea y el resto del programa se entera solo.
Métodos con lógica
Un enum también puede tener métodos que usen this. Mejoramos EstadoPedido para que sepa cuál es el siguiente estado y si el pedido aún se puede cancelar:
public enum EstadoPedido {
PENDIENTE, PAGADO, ENVIADO, ENTREGADO, CANCELADO;
public EstadoPedido siguiente() {
return switch (this) {
case PENDIENTE -> PAGADO;
case PAGADO -> ENVIADO;
case ENVIADO, ENTREGADO -> ENTREGADO;
case CANCELADO -> CANCELADO;
};
}
public boolean sePuedeCancelar() {
return this == PENDIENTE || this == PAGADO;
}
}
EstadoPedido estado = EstadoPedido.PENDIENTE;
estado = estado.siguiente();
System.out.println(estado); // PAGADO
estado = estado.siguiente();
System.out.println(estado + " " + estado.sePuedeCancelar()); // ENVIADO false
Las reglas del negocio quedan dentro del propio tipo, en lugar de repartidas en if por todo el programa.
EnumMap: un mapa con claves de enum
Cuando necesitas asociar un valor a cada constante (por ejemplo, contar cuántos pedidos hay en cada estado), existe EnumMap, un mapa optimizado para claves de tipo enum. Es más rápido que un HashMap y mantiene las claves en el orden de declaración:
List<EstadoPedido> pedidos = List.of(
EstadoPedido.ENVIADO, EstadoPedido.PENDIENTE, EstadoPedido.ENVIADO,
EstadoPedido.ENTREGADO, EstadoPedido.ENVIADO);
Map<EstadoPedido, Integer> conteo = new EnumMap<>(EstadoPedido.class);
for (EstadoPedido e : pedidos) {
conteo.put(e, conteo.getOrDefault(e, 0) + 1);
}
System.out.println(conteo); // {PENDIENTE=1, ENVIADO=3, ENTREGADO=1}
EstadoPedido.class le indica al mapa de qué enum son las claves. También existe EnumSet, un conjunto especializado para enums.
Cuándo usar un enum
Usa un enum cuando:
- El dato tiene un conjunto cerrado de valores conocido al escribir el programa: días de la semana, meses, palos de la baraja, niveles de dificultad, roles de usuario, estados de un proceso.
- Quieres que el compilador impida valores incorrectos.
- Cada valor lleva datos o comportamiento asociados (precio, color, descripción).
No uses un enum cuando los valores cambian sin recompilar: las categorías de productos que da de alta el administrador de la tienda deben estar en una base de datos, no en un enum.
Constantes int | Constantes String | enum | |
|---|---|---|---|
| Impide valores no válidos | No | No | Sí |
| Legible al imprimir | No | Sí | Sí |
| Detecta erratas al compilar | No | No | Sí |
| Puede tener atributos y métodos | No | No | Sí |
switch comprueba que estén todos | No | No | Sí |
Errores frecuentes
- Comparar con
equalsun posiblenull.estado.equals(EstadoPedido.PAGADO)lanzaNullPointerExceptionsiestadoesnull. Usa==. - Usar
valueOfcon texto del usuario sin normalizar. Pasa el texto a mayúsculas y quítale espacios contrim(), y capturaIllegalArgumentExceptionsi puede ser incorrecto. - Guardar el
ordinal()en ficheros o bases de datos. Si reordenas las constantes, los datos quedan mal. - Olvidar el punto y coma tras la última constante cuando el enum tiene atributos o métodos.
- Escribir
case EstadoPedido.PAGADO ->. En Java 21 compila, pero lo habitual es poner solocase PAGADO, y en versiones anteriores era un error. - Añadir un
defaultinnecesario en unswitchde flechas que ya cubre todos los valores. Pierdes el aviso del compilador cuando añadas una constante nueva.
Resumen
| Concepto | Ejemplo |
|---|---|
| Definir | enum EstadoPedido { PENDIENTE, PAGADO } |
| Usar | EstadoPedido e = EstadoPedido.PAGADO; |
| Comparar | e == EstadoPedido.PAGADO |
| Todas las constantes | EstadoPedido.values() |
| De texto a enum | EstadoPedido.valueOf("PAGADO") |
| Nombre y posición | e.name(), e.ordinal() |
| Con atributos | URGENTE("Envío urgente", 7.50, 1) + constructor |
| Con métodos | e.siguiente(), e.sePuedeCancelar() |
| Mapa por enum | new EnumMap<>(EstadoPedido.class) |
En la siguiente lección aprenderás a gestionar lo que pasa cuando algo sale mal, como un valueOf con un texto incorrecto: las excepciones.
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.