Adjuntar valores a Java Enum

1. Introducción

El tipo de enumeración de Java proporciona una forma compatible con el lenguaje de crear y utilizar valores constantes. Al definir un conjunto finito de valores, la enumeración es más segura para los tipos que las variables literales constantes como String o int .

Sin embargo, los valores de enumeración deben ser identificadores válidos y, por convención, se recomienda utilizar SCREAMING_SNAKE_CASE.

Dadas esas limitaciones, el valor de enumeración por sí solo no es adecuado para cadenas legibles por humanos o valores que no son cadenas .

En este tutorial, usaremos las características de la enumeración como una clase Java para adjuntar los valores que queremos.

2. Uso de Java Enum como clase

A menudo creamos una enumeración como una simple lista de valores. Por ejemplo, aquí están las dos primeras filas de la tabla periódica como una enumeración simple :

public enum Element { H, HE, LI, BE, B, C, N, O, F, NE }

Usando la sintaxis anterior, hemos creado diez instancias finales estáticas de la enumeración llamada Element . Si bien esto es muy eficiente, solo hemos capturado los símbolos de los elementos. Y aunque la forma en mayúsculas es apropiada para las constantes de Java, no es como normalmente escribimos los símbolos.

Además, también nos faltan otras propiedades de los elementos de la tabla periódica, como el nombre y el peso atómico.

Aunque el tipo enum tiene un comportamiento especial en Java, podemos agregar constructores, campos y métodos como lo hacemos con otras clases. Debido a esto, podemos mejorar nuestra enumeración para incluir los valores que necesitamos.

3. Agregar un constructor y un campo final

Comencemos agregando los nombres de los elementos. Estableceremos los nombres en una variable final usando un constructor :

public enum Element { H("Hydrogen"), HE("Helium"), // ... NE("Neon"); public final String label; private Element(String label) { this.label = label; } }

En primer lugar, notamos la sintaxis especial en la lista de declaraciones. Así es como se invoca un constructor para tipos de enumeración . Aunque es ilegal usar el operador new para una enumeración , podemos pasar argumentos de constructor en la lista de declaraciones.

Luego declaramos una etiqueta de variable de instancia . Hay algunas cosas a tener en cuenta al respecto.

En primer lugar, elegimos el identificador de la etiqueta en lugar del nombre . Aunque el nombre del campo del miembro está disponible para su uso, elijamos la etiqueta para evitar confusiones con el método predefinido Enum.name () .

En segundo lugar, nuestro campo de etiqueta es definitivo . Si bien los campos de una enumeración no tienen que ser finales , en la mayoría de los casos no queremos que nuestras etiquetas cambien. En el espíritu de que los valores enum sean constantes, esto tiene sentido.

Finalmente, el campo de etiqueta es público. Por tanto, podemos acceder a la etiqueta directamente:

System.out.println(BE.label);

Por otro lado, el campo puede ser privado , al que se accede con un método getLabel () . En aras de la brevedad, este artículo seguirá utilizando el estilo de campo público.

4. Localización de Java Enum Valores

Java proporciona un método valueOf (String) para todos los tipos de enumeración . Por lo tanto, siempre podemos obtener un valor de enumeración basado en el nombre declarado:

assertSame(Element.LI, Element.valueOf("LI"));

Sin embargo, es posible que también deseemos buscar un valor de enumeración en nuestro campo de etiqueta. Para hacer eso podemos agregar un método estático :

public static Element valueOfLabel(String label) { for (Element e : values()) { if (e.label.equals(label)) { return e; } } return null; }

El método estático valueOfLabel () itera los valores del elemento hasta que encuentra una coincidencia. Devuelve nulo si no se encuentra ninguna coincidencia. Por el contrario, se podría generar una excepción en lugar de devolver un valor nulo .

Veamos un ejemplo rápido usando nuestro método valueOfLabel () :

assertSame(Element.LI, Element.valueOfLabel("Lithium"));

5. Almacenamiento en caché de los valores de búsqueda

Podemos evitar iterar los valores de enumeración mediante el uso de un mapa para almacenar en caché las etiquetas . Para hacer esto, definimos un Mapa final estático y lo llenamos cuando se carga la clase:

public enum Element { // ... enum values private static final Map BY_LABEL = new HashMap(); static { for (Element e: values()) { BY_LABEL.put(e.label, e); } } // ... fields, constructor, methods public static Element valueOfLabel(String label) { return BY_LABEL.get(label); } }

Como resultado de estar en caché, los valores de enumeración se iteran solo una vez y el método valueOfLabel () se simplifica.

Como alternativa, podemos construir la caché de manera perezosa cuando se accede por primera vez en el método valueOfLabel () . En ese caso, el acceso al mapa debe sincronizarse para evitar problemas de concurrencia.

6. Adjuntar varios valores

El constructor Enum puede aceptar varios valores . Para ilustrar, agreguemos el número atómico como un int y el peso atómico como un flotante :

public enum Element { H("Hydrogen", 1, 1.008f), HE("Helium", 2, 4.0026f), // ... NE("Neon", 10, 20.180f); private static final Map BY_LABEL = new HashMap(); private static final Map BY_ATOMIC_NUMBER = new HashMap(); private static final Map BY_ATOMIC_WEIGHT = new HashMap(); static { for (Element e : values()) { BY_LABEL.put(e.label, e); BY_ATOMIC_NUMBER.put(e.atomicNumber, e); BY_ATOMIC_WEIGHT.put(e.atomicWeight, e); } } public final String label; public final int atomicNumber; public final float atomicWeight; private Element(String label, int atomicNumber, float atomicWeight) { this.label = label; this.atomicNumber = atomicNumber; this.atomicWeight = atomicWeight; } public static Element valueOfLabel(String label) { return BY_LABEL.get(label); } public static Element valueOfAtomicNumber(int number) { return BY_ATOMIC_NUMBER.get(number); } public static Element valueOfAtomicWeight(float weight) { return BY_ATOMIC_WEIGHT.get(weight); } }

De manera similar, podemos agregar cualquier valor que queramos a la enumeración , como los símbolos de mayúsculas y minúsculas adecuados, "He", "Li" y "Be", por ejemplo.

Además, podemos agregar valores calculados a nuestra enumeración agregando métodos para realizar operaciones.

7. Control de la interfaz

Como resultado de agregar campos y métodos a nuestra enumeración , hemos cambiado su interfaz pública. Por lo tanto, nuestro código, que utiliza los métodos principales Enum name () y valueOf () , no conocerá nuestros nuevos campos.

El método estático valueOf () ya está definido para nosotros por el lenguaje Java. Por lo tanto, no podemos proporcionar nuestra propia implementación valueOf () .

De manera similar, debido a que el método Enum.name () es final, tampoco podemos anularlo .

Como resultado, no hay una forma práctica de utilizar nuestros campos adicionales usando la API estándar de Enum . En cambio, veamos algunas formas diferentes de exponer nuestros campos.

7.1. Anulando toString ()

Anular toString () puede ser una alternativa a anular name () :

@Override public String toString() { return this.label; }

De forma predeterminada, Enum.toString () devuelve el mismo valor que Enum.name ().

7.2. Implementar una interfaz

El tipo de enumeración en Java puede implementar interfaces . Si bien este enfoque no es tan genérico como la API Enum , las interfaces nos ayudan a generalizar.

Consideremos esta interfaz:

public interface Labeled { String label(); }

Por coherencia con el método Enum.name () , nuestro método label () no tiene un prefijo get .

Y, debido a que el método valueOfLabel () es estático , no lo incluimos en nuestra interfaz.

Finalmente, podemos implementar la interfaz en nuestra enumeración :

public enum Element implements Labeled { // ... @Override public String label() { return label; } // ... }

Un beneficio de este enfoque es que la interfaz etiquetada se puede aplicar a cualquier clase, no solo a tipos de enumeración . En lugar de depender de la API Enum genérica , ahora tenemos una API más específica del contexto.

8. Conclusión

En este artículo, hemos explorado muchas características de la implementación de Java Enum . Al agregar constructores, campos y métodos, vemos que la enumeración puede hacer mucho más que constantes literales.

Como siempre, el código fuente completo de este artículo se puede encontrar en GitHub.