Skip to content

1 Introducción a los tipos enumerados

Introducción

Un tipo enumerado (enum type) es un tipo cuyos únicos valores legales consisten en un conjunto fijo de constantes, como por ejemplo las estaciones del año o los palos en una baraja de cartas.

Antes de que los lenguajes nos permitieran definir tipos enumerados, éstos eran simulados declarando un grupo de constantes enteras, una para cada valor posible del tipo. Por ejemplo, para definir los tipos de manzanas y los tipos de naranjas podríamos hacer:

// APPLE TYPES
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;

// ORANGE TYPES
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;

Esta técnica, conocida como el patrón de enumeración entera, tiene muchos defectos, por lo que no se recomienda su uso.

Para empezar, al no definir realmente un tipo, el compilador no puede proporcionarnos seguridad real respecto al tipo. Por ejemplo, podemos pasar un tipo de naranja como argumento de un método que espera recibir un tipo de manzana, o almacenar un tipo de naranja en una variable que debe tener un tipo de manzana.

// Para el compilador esto es correct
int appleType = ORANGE_TEMPLE;

No solo eso, podemos comparar erróneamente tipos de manzanas y tipos de naranjas.

int appleType = APPLE_PIPPIN;
int orangeType = ORANGE_TEMPLE;

if(appleType == orangeType){
    // ...
}

Como Java no proporciona espacios de nombre para grupos de constantes enteras, debemos usar prefijos para evitar conflictos con los nombres de las constantes correspondientes a distintos grupos, como por ejemplo entre ELEMENT_MERCURY y PLANET_MERCURY.

Otro problema es que si se cambia el valor asociado a una constante, todas las clases que la usen seguirán compilando, pero su comportamiento puede llegar a ser incorrecto.

Finalmente, el patrón de enumeración entera tiene el inconveniente de que no hay una forma fácil de traducir las constantes enteras a cadenas imprimibles, es decir, la descripción textual del valor que desea representar.

En otras ocasiones se usa una variante del patrón anterior, pero usando el tipo String como tipo para las constantes en lugar de int. Por ejemplo:

// Apple types
public static final String APPLE_FUJI = "FUJI";
public static final String APPLE_PIPPIN = "PIPPIN";
public static final String APPLE_GRANNY_SMITH = "SMITH";

// Orange types
public static final String ORANGE_NAVEL = "NAVEL";
public static final String ORANGE_TEMPLE = "TEMPLE";

Esta variación se conoce como patrón de enumeración con cadenas, y es aún menos deseable que la anterior, porque aunque proporciona cadenas imprimibles para sus constantes, los desarrolladores pueden tener la tentación de usar en otras clases los valores de cadena directamente en vez de las constantes, por lo que un simple error tipográfico puede conllevar fallos en tiempo de ejecución. Además, la comparación de cadenas es mucho más costosa que la de enteros.

La alternativa adecuada a los patrones de enumeración anteriores es que el lenguaje de programación nos permita definir tipos enumerados, es decir, que nos permita definir un nuevo tipo indicando cuáles son los valores legales para dicho tipo y con qué nombre queremos referirnos a dichos valores.

En algunos lenguajes de programación, estos tipos enumerados son internamente tipos enteros. Sin embargo, en Java cuando definimos un tipo enumerado, estamos definiendo una clase completa, lo que nos proporciona una mayor funcionalidad a la de otros lenguajes de programación. En la versión 5 de Java, se incorporaron al lenguaje los tipos de datos enumerados.

Así, siguiendo el ejemplo anterior, definiríamos las siguientes clases enum:

public enum Apple{
    FUJI, PIPPIN, GRANNY_SMITH
}

public enum Orange{
    NAVEL, TEMPLE
}

Los enums presentan numerosas ventajas frente al uso de los patrones de enumeración entera y de enumeración con cadenas. La más importante es que la clase enum corresponderá a un tipo, por lo que el compilador va a ser capaz de proporcionar comprobación de tipos en tiempo de compilación. Por ejemplo, si se declara que un parámetro es del tipo Apple, el compilador garantiza que cualquier referencia de objeto no nulo pasada al parámetro debe ser alguno de las instancias de Apple válidas que hemos definido en el enum. En caso contrario, se producirá un error de compilación. Ocurre exactamente lo mismo al asignar una expresión de un tipo de enumeración a una variable de un tipo enum, o al usar el operador == para comparar valores de diferentes tipos enum.

Además, los tipos enum definen su propio espacio de nombres, por lo que no hay ningún problema en usar constantes con el mismo nombre en distintos enums.

Otra ventaja es que si cambiamos el orden de las constantes en el enum, esto no afecta al código de los clientes del enum, dado que las constantes no son compiladas en el código cliente, a diferencia de como ocurría en los patrones anteriores.

Definición

Los enumerados se definen con la palabra reservada enum, el nombre del enumerado y luego el conjunto de las constantes, que por las convenciones del lenguaje se escriben en mayúscula. Las constantes de la enumeración son public y static de forma implícita.Ejemplo:

public enum DayOfWeek{
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

Por otra parte, ¿dónde debemos definir un enum? Si va a ser usado desde varios lugares, lo lógico es definir el enum como una clase en su propio fichero. Si su uso está restringido a una única clase cliente, es más razonable definir el enum como una clase interna miembro de dicha clase. Una vez definido el enum, se pueden crear variables de ese tipo:

DayOfWeek day; // day es una variable del tipo de enumeración DayOfWeek

Como day es de tipo DayOfWeek, los únicos valores que se le pueden asignar son los definidos por la enumeración:

day = DayOfWeek.MONDAY; // Se asigna a day el valor MONDAY

El compilador nos da un error si intentamos asignar a una variable enum un tipo que no le corresponde:

DayOfWeek day = Apple.FUJI; //(1)!
  1. ERROR -> falta de correspondencia entre tipos; No se puede convertir de Apple a DayOfWeek