3 Tipo de enumerados compuestos¶
Atributos¶
Los enums no dejan de ser clases, por lo que podemos declararles atributos, que deben ser final, ya que los enums son inmutables por naturaleza. Aunque estos atributos pueden ser public, se recomienda definirlos como private y definir los getters correspondientes si es necesario acceder a los atributos desde fuera del enum.
Para establecer el valor de estos atributos, debemos definir en la clase enum el constructor adecuado y al establecer las instancias pasaremos como argumento los valores deseados que se pasarán al constructor. En el ejemplo, cuando definimos PLUS, le ponemos entre paréntesis la cadena "+". Cuando se cree el objeto para PLUS, se ejecuta el constructor private Operation(String symbol)
y en symbol se pasa "+":
public enum Operation {
PLUS("+"), MINUS("-"), TIMES("*"), DIVIDE("/");
private final String symbol;
Operation(String symbol){
this.symbol = symbol;
}
public String getSymbol() {
return symbol;
}
}
Observa
Del ejemplo se pueden destacar dos cosas importantes y a tener en cuenta:
- El constructor es privado aunque no es necesario indicárselo, ya que si se indica un modificar diferente daría error de compilación. Esto ocurre para evitar crear objetos fuera del enum.
- Cuando se declaran atributos, constructores y/o métodos es obligatorio poner
;
al terminar de declarar los valores del enum.
public class EnumAttributes {
public void show(){
Operation operation1 = Operation.PLUS;
Operation operation2 = Operation.MINUS;
System.out.printf("El atributo de PLUS es una cadena con el valor %s\n", operation1.getSymbol());
System.out.printf("El atributo de MINUS es una cadena con el valor %s\n", operation2.getSymbol());
}
public static void main(String[] args) {
new EnumAttributes().show();
}
}
Métodos¶
Una enumeración no puede heredar de otra clase ni puede actuar como superclase de otra clase, pero por defecto, hereda de la clase java.lang.Enum<E>
y por tanto tiene una serie de métodos heredados, como por ejemplo los métodos toString()
y ordinal()
.
Además, todas las enumeraciones tienen automáticamente dos métodos estáticos predefinidos: values()
y valueOf(String str)
.
Método toString¶
Las clases enum proporcionan de serie implementaciones de alta calidad de todos los métodos de la clase Object
(la clase Enum
hereda de Object
), como el método public String toString()
, que retornará el nombre de la constante asociada a la instancia correspondiente. Por ejemplo Apple.FUJI.toString()
retorna la cadena FUJI
, aunque si no estamos contentos con dicha implementación, podemos sobrescribir nosotros dicho método. Además, las clases enum implementan de serie las interfaces Comparable
y Serializable
.
public class EnumToString {
private void show() {
Operation operation1 = Operation.PLUS;
Operation operation2 = Operation.MINUS;
String operation1ToString = operation1.toString();
String operation2ToString = operation2.toString();
System.out.println(operation1ToString);
System.out.println(operation2ToString);
System.out.println(Operation.DIVIDE);
}
public static void main(String[] args) {
new EnumToString().show();
}
}
El método name()
también devuelve el nombre del enum.
Método valueOf¶
public static E valueOf(String name)
es un método declarado implícitamente que devuelve la instancia del enum que posee asociado el nombre de constante recibida.
public class EnumValueOf {
private void show() {
Operation operation = Operation.valueOf("PLUS"); // Se le asigna a operation la instancia correspondiente a PLUS
System.out.printf("La variable operation es de tipo enum %s y su símbolo es %s", operation,
operation.getSymbol());
}
public static void main(String[] args) {
new EnumValueOf().show();
}
}
¡CUIDADo!
Si el argumento cadena que recibe el método valueOf no corresponde a ninguna constante de enumeración, el método lanza la excepción IllegalArgumentException
.
Método values¶
public static E[] values()
es un método declarado implícitamente que devuelve un array con todas las instancias de la clase enum en el orden en que fueron declarados.
public class EnumValues {
private void show() {
for (Operation operation : Operation.values()) {
System.out.println(operation);
}
}
public static void main(String[] args) {
new EnumValues().show();
}
}
Método ordinal¶
Una enumeración hereda de la clase java.lang.Enum<E>
y por tanto tiene una serie de métodos heredados, como por ejemplo ordinal()
.
public final int ordinal()
devuelve un entero que indica la posición de la constante dentro de la enumeración. La primera constante tiene el valor 0.
public class EnumOrdinal {
private void show() {
for (Operation operation : Operation.values()) {
System.out.println(operation.ordinal());
}
}
public static void main(String[] args) {
new EnumOrdinal().show();
}
}
El problema de usar el método ordinal()
es que el funcionamiento del código cliente depende completamente del orden en el que se definen las constantes en el enum, lo que puede resultar problemático para el mantenimiento del código si en un futuro se cambia dicho orden. En realidad, este método está diseñado para ser utilizado por estructuras de datos sofisticadas basadas en enumeraciones como EnumSet
y EnumMap
. (Se explican en el Tema 11. Colecciones).
Por lo tanto se recomienda no hacer uso del método ordinal()
, sino definir un atributo y asignar un valor para dicho atributo en cada instancia:
public enum Operation {
PLUS("+", 1), MINUS("-", 2), TIMES("*", 3), DIVIDE("/", 4);
private final String symbol;
private final int optionNumber;
Operation(String symbol, int optionNumber){
this.symbol = symbol;
this.optionNumber = optionNumber;
}
public String getSymbol() {
return symbol;
}
public int getOptionNumber() {
return optionNumber;
}
}
Métodos abstractos¶
En algunas ocasiones es necesario asociar un comportamiento ligeramente diferente en cada instancia del enum. En estos casos, debemos declarar un método abstracto en el enum e incluir la implementación concreta de dicho método abstracto en la definición de cada instancia. Estas implementaciones reciben el nombre de constant-specific methods. Por ejemplo:
public enum Operation {
PLUS("+"){
@Override
public double apply(double x, double y) {
return x + y;
}
}, MINUS("-") {
@Override
public double apply(double x, double y) {
return x - y;
}
}, TIMES("*") {
@Override
public double apply(double x, double y) {
return x * y;
}
}, DIVIDE("/") {
@Override
public double apply(double x, double y) {
return x / y;
}
};
private final String symbol;
Operation(String symbol){
this.symbol = symbol;
}
public String getSymbol() {
return symbol;
}
public abstract double apply(double x, double y);
}
public class EnumAbstractMethods {
private void show() {
for (Operation operation :
Operation.values()) {
System.out.printf("%-8s %.2f %s %.2f = %.2f\n", operation + ":", 5f, operation.getSymbol(), 5f,
operation.apply(5, 3));
}
}
public static void main(String[] args) {
new EnumAbstractMethods().show();
}
}
Implementación de interfaces¶
Como ya hemos comentado, un enum no puede heredar de otro enum. Pero hay ocasiones donde tiene sentido que otros programadores puedan "extender" un enum que hayamos proporcionado en nuestra API. Por ejemplo cuando el enum corresponda a códigos de operación sobre una determinada máquina, como por ejemplo el enum Operation , sería recomendable que quien use nuestra API puede "añadir" nuevas operaciones. Para estos casos podemos simular la herencia entre enums definiendo una interfaz con el método de la operación y hacer 1ue nuestro enum implemente dicha interfaz. Por ejemplo
public enum BasicOperation implements Operation {
PLUS("+"){
@Override
public double apply(double x, double y) {
return x + y;
}
}, MINUS("-") {
@Override
public double apply(double x, double y) {
return x - y;
}
}, TIMES("*") {
@Override
public double apply(double x, double y) {
return x * y;
}
}, DIVIDE("/") {
@Override
public double apply(double x, double y) {
return x / y;
}
};
private final String symbol;
BasicOperation(String symbol){
this.symbol = symbol;
}
@Override
public String getSymbol() {
return symbol;
}
}
public enum ExtendedOperation implements Operation{
EXP("^"){
@Override
public double apply(double x, double y) {
return Math.pow(x, y);
}
},
REMAINDER("%"){
@Override
public double apply(double x, double y) {
return x % y;
}
};
private final String symbol;
ExtendedOperation(String symbol) {
this.symbol = symbol;
}
@Override
public String getSymbol() {
return symbol;
}
}
public class InterfacesImplementation {
private void show() {
for (Operation operation : BasicOperation.values()) {
show(operation);
}
for (Operation operation : ExtendedOperation.values()) {
show(operation);
}
}
public void show(Operation operation){
System.out.printf("%-8s %.2f %s %.2f = %.2f\n", operation + ":", 5f, operation.getSymbol(), 5f,
operation.apply(5, 3));
}
public static void main(String[] args) {
new InterfacesImplementation().show();
}
}