50% OFF Plus
--:--:--
¡Obtener!

Enumeraciones Enums

Intermedio
Java
Java
Actualizado: 06/04/2025

¡Desbloquea el curso de Java completo!

IA
Ejercicios
Certificado
Entrar

Mira la lección en vídeo

Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.

Desbloquear Plan Plus

Definición y sintaxis básica

Las enumeraciones (enums) en Java son un tipo especial de clase que permite definir un conjunto fijo de constantes con nombre.

La sintaxis básica para declarar una enumeración es:

public enum DiaSemana {
    LUNES,
    MARTES,
    MIERCOLES,
    JUEVES,
    VIERNES,
    SABADO,
    DOMINGO
}

Cada valor dentro del enum (LUNES, MARTES, etc.) se denomina constante enum y representa una instancia única e inmutable de ese tipo. Por convención, estas constantes se nombran en mayúsculas, siguiendo el estilo de las constantes tradicionales.

Para utilizar un enum, se accede directamente a sus constantes a través del nombre del tipo:

// Declarar una variable de tipo enum
DiaSemana hoy = DiaSemana.LUNES;

// Comparación segura de tipos
if (hoy == DiaSemana.VIERNES) {
    System.out.println("¡Por fin es viernes!");
}

Las enumeraciones funcionan muy bien con las estructuras de control switch:

switch (hoy) {
    case LUNES:
        System.out.println("Inicio de semana");
        break;
    case VIERNES:
        System.out.println("Fin de semana laboral");
        break;
    case SABADO, DOMINGO:  // Múltiples casos en la sintaxis moderna
        System.out.println("Fin de semana");
        break;
    default:
        System.out.println("Día entre semana");
}

Cada constante enum es un objeto único, lo que permite compararlas con el operador == en lugar de .equals().

Todos los enums extienden implícitamente la clase java.lang.Enum, que proporciona algunos métodos:

// Obtener todas las constantes como array
DiaSemana[] todos = DiaSemana.values();

// Recorrer todos los valores
for (DiaSemana dia : DiaSemana.values()) {
    System.out.println(dia);
}

// Convertir una cadena a constante enum
DiaSemana dia = DiaSemana.valueOf("LUNES");  // Debe coincidir exactamente

// Obtener el nombre como cadena
String nombre = DiaSemana.LUNES.name();  // "LUNES"

// Obtener la posición ordinal (base 0)
int posicion = DiaSemana.MIERCOLES.ordinal();  // 2

El método valueOf() lanza una excepción IllegalArgumentException si la cadena proporcionada no coincide exactamente con el nombre de una constante enum, incluyendo el uso de mayúsculas.

Las enumeraciones proporcionan seguridad de tipos (type-safety), lo que significa que el compilador verifica que solo se utilicen constantes válidas del tipo enum correspondiente, evitando errores que podrían ocurrir al usar constantes basadas en enteros o cadenas.

Métodos y constructores en enumeraciones

Las enumeraciones en Java son más que simples listas de constantes. Son clases completas que pueden tener constructores, campos y métodos.

Constructores en enumeraciones

Los constructores en enums son siempre implícitamente privados, incluso si no se especifica el modificador private.

Un constructor de enum se utiliza para inicializar cada constante con valores específicos:

public enum Mes {
    ENERO(31),
    FEBRERO(28),  // En año no bisiesto
    MARZO(31),
    ABRIL(30),
    MAYO(31),
    JUNIO(30),
    JULIO(31),
    AGOSTO(31),
    SEPTIEMBRE(30),
    OCTUBRE(31),
    NOVIEMBRE(30),
    DICIEMBRE(31);

    private final int dias;

    // Constructor para inicializar cada constante
    Mes(int dias) {
        this.dias = dias;
    }

    // Método getter para acceder al campo
    public int getDias() {
        return dias;
    }
}

En este ejemplo, cada constante del enum Mes tiene un valor asociado que representa la cantidad de días en ese mes. Este valor se inicializa a través del constructor cuando se define cada constante.

Para usar este enum con sus valores asociados:

// Obtener los días de un mes específico
int diasEnero = Mes.ENERO.getDias();  // 31
System.out.println("Febrero tiene " + Mes.FEBRERO.getDias() + " días");

// Encontrar meses con 31 días
for (Mes mes : Mes.values()) {
    if (mes.getDias() == 31) {
        System.out.println(mes + " tiene 31 días");
    }
}

Métodos en enumeraciones

Además de los métodos heredados de la clase Enum, se pueden definir métodos propios:

public enum Operacion {
    SUMA, RESTA, MULTIPLICACION, DIVISION;

    // Método que implementa la operación correspondiente
    public double ejecutar(double x, double y) {
        return switch (this) {
            case SUMA -> x + y;
            case RESTA -> x - y;
            case MULTIPLICACION -> x * y;
            case DIVISION -> {
                if (y == 0) {
                    throw new ArithmeticException("División por cero");
                }
                yield x / y;
            }
        };
    }
}

Este enum se puede utilizar para realizar operaciones matemáticas:

double resultado = Operacion.SUMA.ejecutar(5, 3);  // 8.0
System.out.println(Operacion.DIVISION.ejecutar(10, 2));  // 5.0

También se pueden agregar métodos estáticos a los enums, lo que puede ser útil para operaciones que involucran a varias constantes:

public enum Color {
    ROJO(255, 0, 0),
    VERDE(0, 255, 0),
    AZUL(0, 0, 255);

    private final int r;
    private final int g;
    private final int b;

    Color(int r, int g, int b) {
        this.r = r;
        this.g = g;
        this.b = b;
    }

    public int getR() { return r; }
    public int getG() { return g; }
    public int getB() { return b; }

    // Calcular el brillo del color (fórmula simplificada)
    public int getBrillo() {
        return (r + g + b) / 3;
    }

    // Método estático para encontrar el color más brillante
    public static Color masClaro(Color c1, Color c2) {
        return c1.getBrillo() > c2.getBrillo() ? c1 : c2;
    }
}

Es posible sobreescribir los métodos heredados de Enum o Object:

public enum EstadoTarea {
    PENDIENTE, EN_PROGRESO, COMPLETADA, CANCELADA;
    
    @Override
    public String toString() {
        // Convertir de "PENDIENTE" a "Pendiente" para mejor presentación
        return name().charAt(0) + name().substring(1).toLowerCase().replace('_', ' ');
    }
}

Este código personaliza cómo se representan las constantes enum como cadenas:

System.out.println(EstadoTarea.EN_PROGRESO);  // "En progreso" en lugar de "EN_PROGRESO"

Enums con campos y comportamiento personalizado

Guarda tu progreso

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

Enums con campos personalizados

Cada constante enum puede tener sus propios valores para uno o más campos, inicializados a través del constructor:

public enum TamañoCafe {
    PEQUEÑO(250, 2.5),
    MEDIANO(350, 3.0),
    GRANDE(450, 3.5),
    EXTRA_GRANDE(550, 4.0);

    private final int mililitros;
    private final double precio;

    TamañoCafe(int mililitros, double precio) {
        this.mililitros = mililitros;
        this.precio = precio;
    }

    public int getMililitros() {
        return mililitros;
    }

    public double getPrecio() {
        return precio;
    }

    // Método para calcular el precio por mililitro
    public double getPrecioPorML() {
        return precio / mililitros;
    }

    @Override
    public String toString() {
        return name() + " (" + mililitros + "ml, €" + precio + ")";
    }
}

Este enum asocia cada tamaño de café con su volumen en mililitros y su precio, y proporciona métodos para acceder a estos valores y realizar cálculos.

Se puede utilizar así:

TamañoCafe miCafe = TamañoCafe.GRANDE;
System.out.println("Mi café es " + miCafe);  // "GRANDE (450ml, €3.5)"
System.out.println("Precio por ml: " + miCafe.getPrecioPorML() + "€");

// Encontrar el tamaño con mejor relación calidad-precio
TamañoCafe mejorValor = TamañoCafe.PEQUEÑO;
for (TamañoCafe tamaño : TamañoCafe.values()) {
    if (tamaño.getPrecioPorML() < mejorValor.getPrecioPorML()) {
        mejorValor = tamaño;
    }
}
System.out.println("Mejor valor: " + mejorValor);

Enums con comportamiento específico por constante

Cada constante de un enum puede implementar métodos de manera diferente. Esto se logra haciendo que el método sea abstracto en el enum y proporcionando implementaciones específicas para cada constante:

public enum Operacion {
    SUMA {
        @Override
        public double ejecutar(double x, double y) {
            return x + y;
        }
        
        @Override
        public String getSimbolo() {
            return "+";
        }
    },
    
    RESTA {
        @Override
        public double ejecutar(double x, double y) {
            return x - y;
        }
        
        @Override
        public String getSimbolo() {
            return "-";
        }
    },
    
    MULTIPLICACION {
        @Override
        public double ejecutar(double x, double y) {
            return x * y;
        }
        
        @Override
        public String getSimbolo() {
            return "*";
        }
    },
    
    DIVISION {
        @Override
        public double ejecutar(double x, double y) {
            if (y == 0) {
                throw new ArithmeticException("División por cero");
            }
            return x / y;
        }
        
        @Override
        public String getSimbolo() {
            return "/";
        }
    };

    // Método abstracto que cada constante debe implementar
    public abstract double ejecutar(double x, double y);
    
    // Método abstracto para obtener el símbolo de la operación
    public abstract String getSimbolo();
    
    // Método común para todas las constantes
    public String describir() {
        return "Operación " + name().toLowerCase() + " (" + getSimbolo() + ")";
    }
}

Cada constante del enum Operacion proporciona su propia implementación de los métodos abstractos ejecutar y getSimbolo, mientras que todas comparten el método describir.

Se puede utilizar así:

for (Operacion op : Operacion.values()) {
    System.out.println(op.describir());
    System.out.println("5 " + op.getSimbolo() + " 3 = " + op.ejecutar(5, 3));
}

Implementación de interfaces en enums

Los enums también pueden implementar interfaces:

public interface Descriptor {
    String describe();
    String getCategoria();
}

public enum TipoVehiculo implements Descriptor {
    COCHE("Vehículo de cuatro ruedas para transporte de personas", "Terrestre"),
    MOTO("Vehículo de dos ruedas", "Terrestre"),
    BARCO("Vehículo para navegar en agua", "Acuático"),
    AVION("Vehículo para volar", "Aéreo");

    private final String descripcion;
    private final String categoria;

    TipoVehiculo(String descripcion, String categoria) {
        this.descripcion = descripcion;
        this.categoria = categoria;
    }

    @Override
    public String describe() {
        return descripcion;
    }
    
    @Override
    public String getCategoria() {
        return categoria;
    }
}

Se puede utilizar así:

// Usar el enum como implementación de la interfaz
Descriptor desc = TipoVehiculo.BARCO;
System.out.println(desc.describe());
System.out.println("Categoría: " + desc.getCategoria());

// Filtrar vehículos por categoría
for (TipoVehiculo tipo : TipoVehiculo.values()) {
    if (tipo.getCategoria().equals("Terrestre")) {
        System.out.println(tipo + ": " + tipo.describe());
    }
}

Patrones de diseño con enumeraciones

Singleton Pattern

Cada constante enum es, por naturaleza, un singleton (instancia única). Esto convierte a los enums en una buena forma de implementar el patrón Singleton:

public enum DatabaseConnection {
    INSTANCE;
    
    private Connection connection;
    
    // El constructor se llama solo una vez para INSTANCE
    DatabaseConnection() {
        try {
            // Configurar la conexión a la base de datos
            connection = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/mydb", "user", "password");
            System.out.println("Conexión a la base de datos establecida");
        } catch (SQLException e) {
            throw new RuntimeException("Error al conectar a la base de datos", e);
        }
    }
    
    public Connection getConnection() {
        return connection;
    }
    
    public ResultSet executeQuery(String sql) throws SQLException {
        Statement stmt = connection.createStatement();
        return stmt.executeQuery(sql);
    }
    
    public void closeConnection() {
        if (connection != null) {
            try {
                connection.close();
                System.out.println("Conexión a la base de datos cerrada");
            } catch (SQLException e) {
                System.err.println("Error al cerrar la conexión: " + e.getMessage());
            }
        }
    }
}

Esta implementación proporciona una conexión a base de datos singleton, que se puede utilizar en toda la aplicación:

// Uso del singleton
try {
    ResultSet rs = DatabaseConnection.INSTANCE.executeQuery("SELECT * FROM users");
    while (rs.next()) {
        System.out.println(rs.getString("username"));
    }
} catch (SQLException e) {
    e.printStackTrace();
}

// Al finalizar la aplicación
DatabaseConnection.INSTANCE.closeConnection();

Las ventajas de usar un enum para el patrón Singleton incluyen:

  • Es thread-safe por defecto
  • Serialización segura sin implementación adicional
  • Inmunidad a ataques de reflexión
  • Instanciación perezosa garantizada por la JVM

Strategy Pattern

El patrón Strategy permite seleccionar un algoritmo en tiempo de ejecución:

public enum CompressionStrategy {
    ZIP {
        @Override
        public void compress(String source, String target) {
            System.out.println("Comprimiendo " + source + " a " + target + " usando ZIP");
            // Implementación real aquí
        }
        
        @Override
        public void decompress(String source, String target) {
            System.out.println("Descomprimiendo " + source + " a " + target + " usando ZIP");
            // Implementación real aquí
        }
    },
    
    GZIP {
        @Override
        public void compress(String source, String target) {
            System.out.println("Comprimiendo " + source + " a " + target + " usando GZIP");
            // Implementación real aquí
        }
        
        @Override
        public void decompress(String source, String target) {
            System.out.println("Descomprimiendo " + source + " a " + target + " usando GZIP");
            // Implementación real aquí
        }
    },
    
    BZIP2 {
        @Override
        public void compress(String source, String target) {
            System.out.println("Comprimiendo " + source + " a " + target + " usando BZIP2");
            // Implementación real aquí
        }
        
        @Override
        public void decompress(String source, String target) {
            System.out.println("Descomprimiendo " + source + " a " + target + " usando BZIP2");
            // Implementación real aquí
        }
    };
    
    public abstract void compress(String source, String target);
    public abstract void decompress(String source, String target);
    
    // Método de fábrica para seleccionar una estrategia basada en la extensión
    public static CompressionStrategy fromFileExtension(String fileName) {
        if (fileName.endsWith(".zip")) {
            return ZIP;
        } else if (fileName.endsWith(".gz")) {
            return GZIP;
        } else if (fileName.endsWith(".bz2")) {
            return BZIP2;
        } else {
            // Estrategia por defecto
            return ZIP;
        }
    }
}

Uso del patrón Strategy:

// Seleccionar estrategia explícitamente
CompressionStrategy strategy = CompressionStrategy.GZIP;
strategy.compress("file.txt", "file.txt.gz");

// O seleccionar basado en el nombre de archivo
String fileName = "document.txt.bz2";
CompressionStrategy autoStrategy = CompressionStrategy.fromFileExtension(fileName);
autoStrategy.decompress(fileName, "document.txt");

State Pattern

El patrón State permite que un objeto altere su comportamiento cuando su estado interno cambia:

public enum EstadoPedido {
    NUEVO {
        @Override
        public EstadoPedido siguienteEstado() {
            return CONFIRMADO;
        }
        
        @Override
        public boolean puedeModificar() {
            return true;
        }
        
        @Override
        public String getDescripcion() {
            return "Pedido registrado, pendiente de confirmación";
        }
    },
    
    CONFIRMADO {
        @Override
        public EstadoPedido siguienteEstado() {
            return EN_PREPARACION;
        }
        
        @Override
        public boolean puedeModificar() {
            return true;
        }
        
        @Override
        public String getDescripcion() {
            return "Pedido confirmado, pendiente de preparación";
        }
    },
    
    EN_PREPARACION {
        @Override
        public EstadoPedido siguienteEstado() {
            return ENVIADO;
        }
        
        @Override
        public boolean puedeModificar() {
            return false;
        }
        
        @Override
        public String getDescripcion() {
            return "Pedido en proceso de preparación";
        }
    },
    
    ENVIADO {
        @Override
        public EstadoPedido siguienteEstado() {
            return ENTREGADO;
        }
        
        @Override
        public boolean puedeModificar() {
            return false;
        }
        
        @Override
        public String getDescripcion() {
            return "Pedido enviado, en ruta de entrega";
        }
    },
    
    ENTREGADO {
        @Override
        public EstadoPedido siguienteEstado() {
            // Estado final, no hay siguiente
            return this;
        }
        
        @Override
        public boolean puedeModificar() {
            return false;
        }
        
        @Override
        public String getDescripcion() {
            return "Pedido entregado al cliente";
        }
    };
    
    // Métodos abstractos que cada estado debe implementar
    public abstract EstadoPedido siguienteEstado();
    public abstract boolean puedeModificar();
    public abstract String getDescripcion();
}

Ahora podemos usar este enum en una clase Pedido:

public class Pedido {
    private EstadoPedido estado = EstadoPedido.NUEVO;
    private final List<String> items = new ArrayList<>();
    private final String clienteId;
    
    public Pedido(String clienteId) {
        this.clienteId = clienteId;
    }
    
    public void agregarItem(String item) {
        if (estado.puedeModificar()) {
            items.add(item);
        } else {
            throw new IllegalStateException(
                "No se puede modificar el pedido en estado " + estado);
        }
    }
    
    public void avanzarEstado() {
        estado = estado.siguienteEstado();
        System.out.println("Pedido " + clienteId + ": " + estado.getDescripcion());
    }
    
    public EstadoPedido getEstado() {
        return estado;
    }
    
    public List<String> getItems() {
        return Collections.unmodifiableList(items);
    }
}

Y así se utilizaría:

Pedido pedido = new Pedido("CUST-001");
pedido.agregarItem("Laptop");
pedido.agregarItem("Mouse");

pedido.avanzarEstado();  // A CONFIRMADO
pedido.agregarItem("Teclado");  // Aún se puede modificar

pedido.avanzarEstado();  // A EN_PREPARACION
// pedido.agregarItem("Monitor");  // Esto lanzaría una excepción

Command Pattern

El patrón Command encapsula una solicitud como un objeto:

public enum EditorCommand {
    COPY {
        @Override
        public void execute(Editor editor) {
            editor.copy();
        }
        
        @Override
        public String getDescription() {
            return "Copiar texto seleccionado";
        }
    },
    
    CUT {
        @Override
        public void execute(Editor editor) {
            editor.cut();
        }
        
        @Override
        public String getDescription() {
            return "Cortar texto seleccionado";
        }
    },
    
    PASTE {
        @Override
        public void execute(Editor editor) {
            editor.paste();
        }
        
        @Override
        public String getDescription() {
            return "Pegar texto del portapapeles";
        }
    },
    
    UNDO {
        @Override
        public void execute(Editor editor) {
            editor.undo();
        }
        
        @Override
        public String getDescription() {
            return "Deshacer última acción";
        }
    };
    
    public abstract void execute(Editor editor);
    public abstract String getDescription();
    
    // Método para mapear teclas a comandos
    public static EditorCommand fromKeyBinding(String keyBinding) {
        return switch (keyBinding) {
            case "ctrl+c" -> COPY;
            case "ctrl+x" -> CUT;
            case "ctrl+v" -> PASTE;
            case "ctrl+z" -> UNDO;
            default -> throw new IllegalArgumentException("Atajo de teclado no reconocido: " + keyBinding);
        };
    }
}

Uso del patrón Command:

Editor editor = new Editor();

// Ejecutar comandos directamente
EditorCommand.PASTE.execute(editor);

// O a través de atajos de teclado
String userKeyBinding = "ctrl+c";
try {
    EditorCommand command = EditorCommand.fromKeyBinding(userKeyBinding);
    command.execute(editor);
} catch (IllegalArgumentException e) {
    System.out.println(e.getMessage());
}

Alternativa a estructuras if-else y switch complejas

Los enums pueden reemplazar bloques complejos de if-else o switch:

// Enfoque tradicional con if-else
public double calcularDescuento(String dia, double precioBase) {
    if ("LUNES".equals(dia)) {
        return precioBase * 0.1;  // 10% descuento
    } else if ("MARTES".equals(dia)) {
        return precioBase * 0.2;  // 20% descuento
    } else if ("MIERCOLES".equals(dia)) {
        return precioBase * 0.3;  // 30% descuento
    } // ... y más condiciones
}

Usando enums:

public enum DiaSemana {
    LUNES(0.1),
    MARTES(0.2),
    MIERCOLES(0.3),
    JUEVES(0.4),
    VIERNES(0.5),
    SABADO(0),
    DOMINGO(0);
    
    private final double factorDescuento;
    
    DiaSemana(double factorDescuento) {
        this.factorDescuento = factorDescuento;
    }
    
    public double calcularDescuento(double precioBase) {
        return precioBase * factorDescuento;
    }
    
    // Método para convertir String a DiaSemana de forma segura
    public static DiaSemana fromString(String dia) {
        try {
            return valueOf(dia.toUpperCase());
        } catch (IllegalArgumentException e) {
            return DOMINGO;  // Valor por defecto
        }
    }
}

Este enfoque tiene varias ventajas:

  • Seguridad de tipos: no se pueden pasar valores inválidos
  • Encapsulación de la lógica específica a cada constante
  • Mayor legibilidad y mantenibilidad
  • Facilidad para extender el comportamiento
  • Menor propensión a errores

Aprendizajes de esta lección de Java

  • Comprender el concepto y sintaxis básica de las enumeraciones en Java
  • Aprender a declarar y utilizar enumeraciones con constantes
  • Explorar métodos y constructores en enums
  • Implementar patrones de diseño como Singleton y Strategy con enums
  • Descubrir cómo las enumeraciones pueden albergar comportamientos personalizados
  • Aplicar enums en la gestión de estados y comandos de una aplicación

Completa este curso de Java y certifícate

Únete a nuestra plataforma de cursos de programación y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración