Saltar al contenido
enums.java · devschool

Enumeraciones (enum) en Java

Lección 19 de 26 · 10 min de lectura · Actualizado el

En esta lección
  1. El problema de las constantes int y String
  2. Definir y usar un enum
  3. Enums en switch
  4. Métodos que traen todos los enums
  5. Enums con atributos, constructor y métodos
  6. EnumMap: un mapa con claves de enum
  7. Cuándo usar un enum
  8. Errores frecuentes
  9. Resumen

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, no ENVIADO.
  • 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 enum en lugar de class, 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 EstadoPedido solo puede contener uno de esos cinco valores (o null). 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 es null.

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 como String: "ENVIADO". Por defecto toString() 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 el name().

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 intConstantes Stringenum
Impide valores no válidosNoNoSí
Legible al imprimirNoSíSí
Detecta erratas al compilarNoNoSí
Puede tener atributos y métodosNoNoSí
switch comprueba que estén todosNoNoSí

Errores frecuentes

  • Comparar con equals un posible null. estado.equals(EstadoPedido.PAGADO) lanza NullPointerException si estado es null. Usa ==.
  • Usar valueOf con texto del usuario sin normalizar. Pasa el texto a mayúsculas y quítale espacios con trim(), y captura IllegalArgumentException si 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 solo case PAGADO, y en versiones anteriores era un error.
  • Añadir un default innecesario en un switch de flechas que ya cubre todos los valores. Pierdes el aviso del compilador cuando añadas una constante nueva.

Resumen

ConceptoEjemplo
Definirenum EstadoPedido { PENDIENTE, PAGADO }
UsarEstadoPedido e = EstadoPedido.PAGADO;
Comparare == EstadoPedido.PAGADO
Todas las constantesEstadoPedido.values()
De texto a enumEstadoPedido.valueOf("PAGADO")
Nombre y posicióne.name(), e.ordinal()
Con atributosURGENTE("Envío urgente", 7.50, 1) + constructor
Con métodose.siguiente(), e.sePuedeCancelar()
Mapa por enumnew 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

[Java] ¿Qué imprime este código?
enum Talla { S, M, L, XL }

System.out.println(Talla.valueOf("L").ordinal() + " " + Talla.values().length);

[Java] Con el enum Talla { S, M, L, XL }, ¿qué hace Talla.valueOf("m")?

[Java] ¿Qué imprime este código?
enum Envio {
    NORMAL(4), URGENTE(1);

    private final int dias;
    Envio(int dias) { this.dias = dias; }
    int getDias() { return dias; }
}

System.out.println(Envio.URGENTE.getDias() + Envio.NORMAL.getDias());

[Java] ¿Cuál es la principal ventaja de un enum frente a constantes int como PENDIENTE = 0, PAGADO = 1?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.