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 PlusDefinició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.
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