Cómo usar enumeraciones con seguridad de tipos en Java

El código Java que utiliza tipos enumerados tradicionales es problemático. Java 5 nos brindó una mejor alternativa en forma de enumeraciones con seguridad de tipos. En este artículo, le presento los tipos enumerados y las enumeraciones de seguridad de tipos, le muestro cómo declarar una enumeración de seguridad de tipos y usarla en una declaración de cambio, y analizo la personalización de una enumeración de seguridad de tipos agregando datos y comportamientos. Termino el artículo explorando la clase.java.lang.Enum

descargar Obtener el código Descargue el código fuente para ver ejemplos en este tutorial de Java 101. Creado por Jeff Friesen para JavaWorld /.

De tipos enumerados a enumeraciones seguras de tipos

Un tipo enumerado especifica un conjunto de constantes relacionadas como sus valores. Los ejemplos incluyen una semana de días, las direcciones estándar de la brújula norte / sur / este / oeste, las denominaciones de moneda de una moneda y los tipos de fichas de un analizador léxico.

Los tipos enumerados se han implementado tradicionalmente como secuencias de constantes enteras, lo que se demuestra mediante el siguiente conjunto de constantes de dirección:

estático final int DIR_NORTH = 0; estático final int DIR_WEST = 1; estático final int DIR_EAST = 2; estático final int DIR_SOUTH = 3;

Hay varios problemas con este enfoque:

  • Falta de seguridad de tipos: debido a que una constante de tipo enumerada es solo un número entero, se puede especificar cualquier número entero donde se requiera la constante. Además, se pueden realizar operaciones de suma, resta y otras operaciones matemáticas con estas constantes; por ejemplo, (DIR_NORTH + DIR_EAST) / DIR_SOUTH), que no tiene sentido.
  • Espacio de nombres no presente: las constantes de un tipo enumerado deben tener el prefijo de algún tipo de identificador único (con suerte) (por ejemplo, DIR_) para evitar colisiones con las constantes de otro tipo enumerado.
  • Fragilidad: debido a que las constantes de tipo enumeradas se compilan en archivos de clase donde se almacenan sus valores literales (en grupos de constantes), cambiar el valor de una constante requiere que se reconstruyan estos archivos de clase y los archivos de clase de aplicación que dependen de ellos. De lo contrario, se producirá un comportamiento indefinido en tiempo de ejecución.
  • Falta de información: cuando se imprime una constante, se genera su valor entero. Esta salida no le dice nada sobre lo que representa el valor entero. Ni siquiera identifica el tipo enumerado al que pertenece la constante.

Puede evitar los problemas de "falta de seguridad de tipos" y "falta de información" mediante el uso de java.lang.Stringconstantes. Por ejemplo, puede especificar static final String DIR_NORTH = "NORTH";. Aunque el valor constante es más significativo, las Stringconstantes basadas en -aún sufren problemas de "espacio de nombres no presente" y fragilidad. Además, a diferencia de las comparaciones de enteros, no puede comparar valores de cadena con los operadores ==y !=(que solo comparan referencias).

Estos problemas hicieron que los desarrolladores inventaran una alternativa basada en clases conocida como Typesafe Enum . Este patrón ha sido ampliamente descrito y criticado. Joshua Bloch introdujo el patrón en el ítem 21 de su Effective Java Programming Language Guide (Addison-Wesley, 2001) y señaló que tiene algunos problemas; es decir, que es incómodo agregar constantes de enumeración seguras de tipos en conjuntos, y que las constantes de enumeración no se pueden usar en switchdeclaraciones.

Considere el siguiente ejemplo del patrón de enumeración con seguridad de tipos. La Suitclase muestra cómo puede usar la alternativa basada en clases para presentar un tipo enumerado que describe los cuatro palos de cartas (tréboles, diamantes, corazones y espadas):

Traje de clase final pública // No debería poder subclase de Traje. {CLUBES de Traje público estático final = nuevo Traje (); Traje final estático público DIAMANTES = nuevo Traje (); Traje final estático público HEARTS = new Suit (); Traje final estático público SPADES = new Suit (); private Suit () {} // No debería poder introducir constantes adicionales. }

Para usar esta clase, debe introducir una Suitvariable y asignarla a una de Suitlas constantes de la siguiente manera:

Traje traje = Suit.DIAMONDS;

A continuación, es posible que desee interrogar suiten una switchdeclaración como esta:

cambiar (traje) {caso Traje.CLUBS: System.out.println ("clubes"); romper; caso Suit.DIAMONDS: System.out.println ("diamantes"); romper; caso Suit.HEARTS: System.out.println ("corazones"); romper; caso Suit.SPADES: System.out.println ("espadas"); }

Sin embargo, cuando el compilador de Java lo encuentra Suit.CLUBS, informa de un error que indica que se requiere una expresión constante. Puede intentar solucionar el problema de la siguiente manera:

cambiar (traje) {caso CLUBS: System.out.println ("clubes"); romper; caso DIAMANTES: System.out.println ("diamantes"); romper; case HEARTS: System.out.println ("corazones"); romper; case SPADES: System.out.println ("espadas"); }

Sin embargo, cuando el compilador lo encuentra CLUBS, informará de un error que indica que no pudo encontrar el símbolo. E incluso si se coloca Suiten un paquete, el paquete importado, y estáticamente importado estas constantes, el compilador se queja de que no puede convertir Suita intcuando se encuentran suiten switch(suit). Con respecto a cada uno case, el compilador también informaría que se requiere una expresión constante.

Java no admite el patrón Typesafe Enum con switchdeclaraciones. Sin embargo, sí introdujo la característica de lenguaje de enumeración de tipo seguro para encapsular los beneficios del patrón mientras resuelve sus problemas, y esta característica es compatible switch.

Declarar una enumeración con seguridad de tipos y usarla en una declaración de cambio

Una declaración de enumeración segura de tipos simple en código Java se parece a sus contrapartes en los lenguajes C, C ++ y C #:

enum Dirección {NORTH, WEST, EAST, SOUTH}

Esta declaración usa la palabra clave enumpara introducir Directioncomo una enumeración segura de tipos (un tipo especial de clase), en la que se pueden agregar métodos arbitrarios y se pueden implementar interfaces arbitrarias. El NORTH, WEST, EAST, y SOUTHconstantes de enumeración se implementan como cuerpos de clase-constantes específicas que definen clases anónimos extienden la envolvente Directionclase.

Directiony otras enumeraciones typesafe extienden  y heredan varios métodos, incluyendo , y , de esta clase. Exploraremos más adelante en este artículo.Enum values()toString()compareTo()Enum

El Listado 1 declara la enumeración mencionada anteriormente y la usa en una switchdeclaración. También muestra cómo comparar dos constantes enum, para determinar qué constante viene antes que la otra constante.

Listado 1: TEDemo.java(versión 1)

public class TEDemo {enum Direction {NORTH, WEST, EAST, SOUTH} public static void main (String [] args) {for (int i = 0; i <Direction.values ​​(). length; i ++) {Direction d = Direction .valores () [i]; System.out.println (d); cambiar (d) {caso NORTE: System.out.println ("Mover al norte"); romper; case WEST: System.out.println ("Mover al oeste"); romper; case EAST: System.out.println ("Mover al este"); romper; caso SUR: System.out.println ("Mover al sur"); romper; predeterminado: afirmar falso: "dirección desconocida"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}

El Listado 1 declara la Directionenumeración de seguridad de tipos e itera sobre sus miembros constantes, que values()devuelve. Para cada valor, la switchdeclaración (mejorada para admitir enumeraciones con seguridad de tipos) elige el caseque corresponde al valor de  d y genera un mensaje apropiado. (No se debe prefijar una constante de enumeración, por ejemplo, NORTHcon su tipo de enumeración). Por último, el Listado 1 evalúa Direction.NORTH.compareTo(Direction.SOUTH)para determinar si NORTHviene antes SOUTH.

Compile el código fuente de la siguiente manera:

javac TEDemo.java

Ejecute la aplicación compilada de la siguiente manera:

Java TEDemo

Debe observar el siguiente resultado:

NORTE Mover al norte OESTE Mover al oeste ESTE Mover al este SUR Mover al sur -3

La salida revela que el toString()método heredado devuelve el nombre de la constante enum, y eso NORTHviene antes SOUTHen una comparación de estas constantes enum.

Agregar datos y comportamientos a una enumeración con seguridad de tipos

Puede agregar datos (en forma de campos) y comportamientos (en forma de métodos) a una enumeración con seguridad de tipos. Por ejemplo, suponga que necesita introducir una enumeración para las monedas canadienses y que esta clase debe proporcionar los medios para devolver el número de monedas de cinco centavos, diez centavos, veinticinco centavos o dólares contenidos en una cantidad arbitraria de centavos. El Listado 2 le muestra cómo realizar esta tarea.

Listado 2: TEDemo.java(versión 2)

enum Coin {NICKEL (5), // las constantes deben aparecer primero DIME (10), QUARTER (25), DOLLAR (100); // el punto y coma es obligatorio private final int valueInPennies; Moneda (int valueInPennies) {this.valueInPennies = valueInPennies; } int toCoins (int pennies) {devolver pennies / valueInPennies; }} public class TEDemo {public static void main (String [] args) {if (args.length! = 1) {System.err.println ("uso: java TEDemo amountInPennies"); regreso; } int pennies = Integer.parseInt (args [0]); for (int i = 0; i <Coin.values ​​(). length; i ++) System.out.println (pennies + "pennies contains" + Coin.values ​​() [i] .toCoins (pennies) + "" + Coin .valores () [i] .toString (). toLowerCase () + "s"); }}

El Listado 2 primero declara una Coinenumeración. Una lista de constantes parametrizadas identifica cuatro tipos de monedas. El argumento pasado a cada constante representa el número de centavos que representa la moneda.

El argumento que se pasa a cada constante se pasa al Coin(int valueInPennies)constructor, que guarda el argumento en el valuesInPenniescampo de la instancia. Se accede a esta variable desde dentro del toCoins()método de instancia. Se divide en el número de monedas de un centavo pasados a toCoin()'s penniesparámetro, y este método devuelve el resultado, que pasa a ser el número de monedas en la denominación monetaria descrito por la Coinconstante.

En este punto, ha descubierto que puede declarar campos de instancia, constructores y métodos de instancia en una enumeración con seguridad de tipos. Después de todo, una enumeración con seguridad de tipos es esencialmente un tipo especial de clase Java.

El método de TEDemola clase main()primero verifica que se haya especificado un único argumento de línea de comando. Este argumento se convierte en un número entero llamando al método de la java.lang.Integerclase parseInt(), que analiza el valor de su argumento de cadena en un número entero (o lanza una excepción cuando se detecta una entrada no válida). Tendré más que decir sobre Integersus clases primarias en un futuro artículo de Java 101 .

Moving forward, main() iterates over Coin’s constants. Because these constants are stored in a Coin[] array, main() evaluates Coin.values().length to determine the length of this array. For each iteration of loop index i, main() evaluates Coin.values()[i] to access the Coin constant. It invokes each of toCoins() and toString() on this constant, which further proves that Coin is a special kind of class.

Compile the source code as follows:

javac TEDemo.java

Run the compiled application as follows:

java TEDemo 198

You should observe the following output:

198 pennies contains 39 nickels 198 pennies contains 19 dimes 198 pennies contains 7 quarters 198 pennies contains 1 dollars

Exploring the Enum class

The Java compiler considers enum to be syntactic sugar. Upon encountering a typesafe enum declaration, it generates a class whose name is specified by the declaration. This class subclasses the abstract Enum class, which serves as the base class for all typesafe enums.

Enum’s formal type parameter list looks ghastly, but it’s not that hard to understand. For example, in the context of Coin extends Enum, you would interpret this formal type parameter list as follows:

  • Any subclass of Enum must supply an actual type argument to Enum. For example, Coin’s header specifies Enum.
  • The actual type argument must be a subclass of Enum. For example, Coin is a subclass of Enum.
  • A subclass of Enum (such as Coin) must follow the idiom that it supplies its own name (Coin) as an actual type argument.

Examine Enum’s Java documentation and you’ll discover that it overrides java.lang.Object's clone(), equals(), finalize(), hashCode(), and toString() methods. Except for toString(), all of these overriding methods are declared final so that they cannot be overridden in a subclass:

  • clone() is overridden to prevent constants from being cloned so that there is never more than one copy of a constant; otherwise, constants could not be compared via == and !=.
  • equals()se anula para comparar constantes a través de sus referencias. Las constantes con las mismas identidades ( ==) deben tener el mismo contenido ( equals()), y diferentes identidades implican diferentes contenidos.
  • finalize() se anula para garantizar que las constantes no se puedan finalizar.
  • hashCode()se anula porque equals()se anula.
  • toString() se anula para devolver el nombre de la constante.

Enumtambién proporciona sus propios métodos. Estos métodos incluyen los finalcompareTo() ( Enumimplementa la java.lang.Comparableinterfaz), getDeclaringClass(), name(), y ordinal()métodos: