¿Cuál es la ventaja de una enumeración de Java frente a una clase con campos finales estáticos públicos?

147

Estoy muy familiarizado con C # pero estoy empezando a trabajar más en Java. Esperaba aprender que las enumeraciones en Java eran básicamente equivalentes a las de C #, pero aparentemente este no es el caso. Inicialmente, me entusiasmó saber que las enumeraciones de Java podrían contener múltiples datos que parecen muy ventajosos ( http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html ). Sin embargo, desde entonces he descubierto que faltan muchas características que son triviales en C #, como la capacidad de asignar fácilmente un determinado elemento a un cierto valor y, en consecuencia, la capacidad de convertir un número entero en una enumeración sin una cantidad decente de esfuerzo ( es decir, convertir el valor entero en Java Enum correspondiente ).

Entonces mi pregunta es esta: ¿hay algún beneficio para las enumeraciones de Java sobre una clase con un montón de campos finales estáticos públicos? ¿O simplemente proporciona una sintaxis más compacta?

EDITAR: Déjame ser más claro. ¿Cuál es el beneficio de las enumeraciones Java sobre una clase con un montón de campos finales estáticos públicos del mismo tipo ? Por ejemplo, en el ejemplo de los planetas en el primer enlace, ¿cuál es la ventaja de una enumeración sobre una clase con estas constantes públicas:

public static final Planet MERCURY = new Planet(3.303e+23, 2.4397e6);
public static final Planet VENUS = new Planet(4.869e+24, 6.0518e6);
public static final Planet EARTH = new Planet(5.976e+24, 6.37814e6);
public static final Planet MARS = new Planet(6.421e+23, 3.3972e6);
public static final Planet JUPITER = new Planet(1.9e+27, 7.1492e7);
public static final Planet SATURN = new Planet(5.688e+26, 6.0268e7);
public static final Planet URANUS = new Planet(8.686e+25, 2.5559e7);
public static final Planet NEPTUNE = new Planet(1.024e+26, 2.4746e7);

Por lo que puedo decir, la respuesta de casablanca es la única que satisface esto.

Craig W
fuente
44
@Bohemian: Puede que no sea un duplicado, ya que el OP solo menciona public static finalcampos, que pueden ser valores escritos y no necesariamente ints.
casablanca
1
@Shahzeb Apenas. Claramente, usar enumeraciones en lugar de constantes de cadena es una idea EXTREMADAMENTE buena y más que recomendable. Type-safety, no necesita funciones estáticas de utilidad, etc., no hay absolutamente ninguna razón para usar cadenas.
Voo
1
@Voo Sí, sabía que habrá desacuerdos. Y aquí hay uno en 49 segundos. Las enumeraciones son geniales (y las amo y las uso muy a menudo), pero ¿qué tipo de seguridad necesitas cuando declaras una constante o la usas? Es una exageración crear una enumeración cada vez que necesite declarar una constante para String literal.
Shahzeb
44
@Shahzeb Si tiene una sola variable, asegúrese de usar una cadena, no puede suceder mucho aquí (un solo valor es bastante inútil como parámetro). Pero tenga en cuenta que estamos hablando de constante S, por lo que ahora estamos hablando de pasarlos probablemente a funciones, etc. ¿ Necesitamos seguridad de tipografía? Bueno, no, pero la mayoría de las personas consideran los tipos de estilo c de "todo es un void*" buen estilo y puede detener los errores (¡especialmente si pasa más de un parámetro enum / string!). También pone las constantes en su propio espacio de nombres, etc. Contrariamente a eso, no hay una ventaja real de tener variables simples.
Voo
3
@Bohemian: No veo cómo. Con ints, no hay seguridad de tipo porque uno podría pasar cualquier valor. Los objetos mecanografiados, por otro lado, no son diferentes de las enumeraciones en términos de seguridad de tipos.
casablanca

Respuestas:

78

Técnicamente, uno podría ver las enumeraciones como una clase con un montón de constantes escritas, y de hecho así es como las constantes de enumeración se implementan internamente. El uso de un enumembargo le da métodos útiles ( Enum javadoc ) que de otra manera tendrían que aplicar a sí mismo, como por ejemplo Enum.valueOf.

casablanca
fuente
14
También hay .values()que iterar sobre la lista de valores.
h3xStream
1
Esta parece ser la respuesta correcta, aunque no es muy satisfactoria. En mi opinión, apenas valió la pena para Java agregar soporte para enumeraciones solo para la sintaxis más compacta y el acceso a los métodos Enum.
Craig W
77
@Craig, tus instintos son correctos: esta es una respuesta muy mala, ya que se perdió por completo el propósito de las enumeraciones. Vea mis comentarios debajo de la pregunta para ver una parte de las razones.
Bohemio
1
@Bohemian: No "perdí el propósito" de las enumeraciones, las uso todo el tiempo. Vea mi respuesta a su comentario arriba.
casablanca
1
El método Enum.valuesOf devuelve el mismo objeto si se llama dos veces?
Emre Aktürk
104
  1. Tipo de seguridad y valor de seguridad.
  2. Singleton garantizado.
  3. Capacidad para definir y anular métodos.
  4. Capacidad para usar valores en switchdeclaraciones de casedeclaraciones sin calificación.
  5. Secuencialización de valores incorporada a través de ordinal().
  6. Serialización por nombre, no por valor, lo que ofrece un grado de protección a futuro.
  7. EnumSety EnumMapclases.
Marqués de Lorne
fuente
19
Habiendo dicho todo eso, cada vez que pongo código en un Enum me arrepiento.
Marqués de Lorne
44
¿Por qué te arrepientes? Yo nunca ...
glglgl
2
@glglgl Porque puso el código específico de la aplicación en un lugar donde sentí que realmente no pertenecía, que en realidad solo definía un conjunto de valores. Si tuviera que hacerlo nuevamente, lo habría incluido en una de las numerosas switchdeclaraciones que fueron la motivación original para usar un Enum.
Marqués de Lorne
72

Nadie mencionó la capacidad de usarlos en switchdeclaraciones; Voy a tirar eso también.

Esto permite que las enumeraciones complejas arbitrariamente se usen de manera limpia sin usar secuencias instanceofpotencialmente confusas ifo valores de conmutación no string / int. El ejemplo canónico es una máquina de estados.

Dave Newton
fuente
De todos modos, no mencionó ningún beneficio de las enumeraciones frente a los campos estáticos. Puede usar suficientes tipos en las declaraciones de cambio con campos estáticos. Op Necesita funcionalidades reales o diferencias de rendimiento
Genaut
@Genaut El beneficio es que las enumeraciones tienen más funcionalidad que una cadena o int: la pregunta era sobre las diferencias, que proporcioné. El OP ya sabe lo que son las enumeraciones, y nadie más había mencionado declaraciones de cambio cuando publiqué esto hace 4.5 años, y al menos algunas personas encontraron que proporcionaba nueva información ¯_ (ツ) _ / ¯
Dave Newton
44

La principal ventaja es la seguridad de tipo. Con un conjunto de constantes, cualquier valor del mismo tipo intrínseco podría usarse, introduciendo errores. Con una enumeración solo se pueden usar los valores aplicables.

Por ejemplo

public static final int SIZE_SMALL  = 1;
public static final int SIZE_MEDIUM = 2;
public static final int SIZE_LARGE  = 3;

public void setSize(int newSize) { ... }

obj.setSize(15); // Compiles but likely to fail later

vs

public enum Size { SMALL, MEDIUM, LARGE };

public void setSize(Size s) { ... }

obj.setSize( ? ); // Can't even express the above example with an enum
Jim Garrison
fuente
3
las clases también son de tipo seguro ...: / (suponiendo que el campo estático sea del tipo de la clase contenedor)
h3xStream
Todavía podría pasarle un valor no válido, pero sería un error de tiempo de compilación que es mucho más fácil de detectar.
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
2
Podría llamar setSize(null)a su segundo ejemplo, pero es probable que falle mucho antes que el error del primer ejemplo.
Jeffrey
42

Hay menos confusión. Tomar Fontpor ejemplo. Tiene un constructor que toma el nombre del Fontque deseas, su tamaño y su estilo ( new Font(String, int, int)). Hasta el día de hoy no puedo recordar si el estilo o el tamaño van primero. Si Fonthabía utilizado una enumpara todos sus diferentes estilos ( PLAIN, BOLD, ITALIC, BOLD_ITALIC), su constructor se vería así Font(String, Style, int), evitando cualquier confusión. Desafortunadamente, enumno existían cuando Fontse creó la clase, y dado que Java debe mantener la compatibilidad inversa, esta ambigüedad siempre nos afectará.

Por supuesto, esto es solo un argumento para usar un en enumlugar de public static finalconstantes. Las enumeraciones también son perfectas para los singletons y la implementación del comportamiento predeterminado al tiempo que permiten una personalización posterior (es decir, el patrón de estrategia ). Un ejemplo de esto último es java.nio.file's OpenOptiony StandardOpenOption: si un desarrollador quería crear su propia no estándar OpenOption, lo que pudo.

Jeffrey
fuente
Su caso 'Fuente' sigue siendo ambiguo si se pidieron dos de las mismas enumeraciones. La respuesta canónica a ese problema es lo que muchos idiomas llaman Parámetros con nombre . Eso o mejor soporte de firma de método IDE.
aaaaaa
1
@aaaaaa No he visto muchos casos en los que un constructor tomaría dos de lo mismo enumsin usar varargs o a Setpara tomar un número arbitrario de ellos.
Jeffrey
@aaaaaa El problema principal con los parámetros con nombre se basa en los detalles de implementación (el nombre del parámetro). Puedo hacer una interfaz interface Foo { public void bar(String baz); }. Alguien hace una clase que invoca bar: someFoo.bar(baz = "hello");. Cambio la firma de Foo::bara public void bar(String foobar). Ahora, la persona que llamó someFoodeberá modificar su código si todavía quiere que funcione.
Jeffrey
Tampoco recuerdo haber visto tipos de enumeración consecutivos, pero pensé que uno común podría ser DAY_OF_WEEK o algo similar. Y un buen punto sobre la interfaz, no había pensado en eso. Personalmente, tomaría ese problema sobre la ambigüedad generalizada causada por parámetros sin nombre, que requieren un soporte IDE más fuerte. Sin embargo, entiendo que es una decisión decisiva, y que los cambios importantes en la API son algo a considerar seriamente.
aaaaaa
26

Aquí hay muchas buenas respuestas, pero ninguna menciona que hay implementaciones altamente optimizadas de las clases / interfaces API de la colección específicamente para enumeraciones :

Estas clases específicas de enumeración solo aceptan Enuminstancias (las EnumMapúnicas solo aceptan Enums como claves), y siempre que sea posible, vuelven a la representación compacta y la manipulación de bits en su implementación.

¿Qué significa esto?

Si nuestro Enumtipo no tiene más de 64 elementos (la mayoría de los Enumejemplos de la vida real calificarán para esto), las implementaciones almacenan los elementos en un solo longvalor, cada Enuminstancia en cuestión se asociará con un poco de esta longitud de 64 bits long. Agregar un elemento a un EnumSetes simplemente establecer el bit apropiado en 1, eliminarlo es solo establecer ese bit en 0. ¡Probar si un elemento está en el Setes solo una prueba de máscara de bits! ¡Ahora tienes que amar Enumpor esto!

icza
fuente
1
Sabía sobre estos dos antes, pero aprendí mucho de tu What does this mean?sección. Sabía que había una división en la implementación en el tamaño 64, pero en realidad no sabía por qué
Christopher Rucinski el
15

ejemplo:

public class CurrencyDenom {
   public static final int PENNY = 1;
 public static final int NICKLE = 5;
 public static final int DIME = 10;
public static final int QUARTER = 25;}

Limitación de las constantes de Java

1) Sin seguridad de tipo : en primer lugar, no es de tipo seguro; puede asignar cualquier valor int válido a int, por ejemplo, 99, aunque no hay una moneda para representar ese valor.

2) Sin impresión significativa : el valor de impresión de cualquiera de estas constantes imprimirá su valor numérico en lugar del nombre significativo de la moneda, por ejemplo, cuando imprima NICKLE imprimirá "5" en lugar de "NICKLE"

3) Sin espacio de nombres : para acceder a la constante currencyDenom necesitamos prefijar el nombre de la clase, por ejemplo, CurrencyDenom.PENNY en lugar de simplemente usar PENNY, aunque esto también se puede lograr mediante la importación estática en JDK 1.5

Ventaja de enum

1) Las enumeraciones en Java son de tipo seguro y tienen su propio espacio de nombres. Significa que su enumeración tendrá un tipo, por ejemplo, "Moneda" en el siguiente ejemplo y no puede asignar ningún valor que no sea el especificado en Constantes de enumeración.

public enum Currency {PENNY, NICKLE, DIME, QUARTER};

Currency coin = Currency.PENNY; coin = 1; //compilation error

2) Enum en Java son tipos de referencia como clase o interfaz y puede definir constructor, métodos y variables dentro de Java Enum, lo que lo hace más poderoso que Enum en C y C ++ como se muestra en el siguiente ejemplo de tipo Java Enum.

3) Puede especificar valores de constantes enum en el momento de la creación como se muestra en el siguiente ejemplo: public enum Currency {PENNY (1), NICKLE (5), DIME (10), QUARTER (25)}; Pero para que esto funcione, debe definir una variable miembro y un constructor porque PENNY (1) en realidad está llamando a un constructor que acepta el valor int, vea el siguiente ejemplo.

public enum Currency {
    PENNY(1), NICKLE(5), DIME(10), QUARTER(25);
    private int value;

    private Currency(int value) {
            this.value = value;
    }
}; 

Referencia: https://javarevisited.blogspot.com/2011/08/enum-in-java-example-tutorial.html

Disidente
fuente
11

El primer beneficio de las enumeraciones, como ya ha notado, es la simplicidad de sintaxis. Pero el punto principal de las enumeraciones es proporcionar un conjunto conocido de constantes que, por defecto, forman un rango y ayudan a realizar un análisis de código más completo a través de verificaciones de seguridad de tipo y valor.

Esos atributos de las enumeraciones ayudan tanto a un programador como a un compilador. Por ejemplo, supongamos que ve una función que acepta un número entero. ¿Qué podría significar ese entero? ¿Qué tipo de valores puedes pasar? Realmente no lo sabes de inmediato. Pero si ve una función que acepta enum, conoce muy bien todos los valores posibles que puede pasar.

Para el compilador, las enumeraciones ayudan a determinar un rango de valores y, a menos que asigne valores especiales a los miembros de enumeración, son rangos bien desde 0 en adelante. Esto ayuda a localizar automáticamente errores en el código a través de verificaciones de seguridad de tipo y más. Por ejemplo, el compilador puede advertirle que no maneja todos los valores de enumeración posibles en su declaración de cambio (es decir, cuando no tiene defaultmayúsculas y minúsculas y maneja solo uno de los N valores de enumeración). También le advierte cuando convierte un entero arbitrario en enum porque el rango de valores de enum es menor que el entero y eso a su vez puede provocar errores en la función que realmente no acepta un entero. Además, generar una tabla de salto para el interruptor se vuelve más fácil cuando los valores son de 0 en adelante.

Esto no solo es cierto para Java, sino también para otros lenguajes con una estricta verificación de tipos. C, C ++, D, C # son buenos ejemplos.


fuente
4

Una enumeración es implícitamente final, con unos constructores privados, todos sus valores son del mismo tipo o un subtipo, puede obtener todos sus valores usando values(), obtiene su name()o ordinal()valor o puede buscar una enumeración en número o nombre.

También puede definir subclases (aunque no sea final, algo que no puede hacer de otra manera)

enum Runner implements Runnable {
    HI {
       public void run() {
           System.out.println("Hello");
       }
    }, BYE {
       public void run() {
           System.out.println("Sayonara");
       }
       public String toString() {
           return "good-bye";
       }
    }
 }

 class MYRunner extends Runner // won't compile.
Peter Lawrey
fuente
4

enum Beneficios:

  1. Las enumeraciones son de tipo seguro, los campos estáticos no
  2. Hay un número finito de valores (no es posible pasar un valor de enumeración no existente. Si tiene campos de clase estáticos, puede cometer ese error)
  3. Cada enumeración puede tener múltiples propiedades (campos / captadores) asignadas: encapsulación. También algunos métodos simples: YEAR.toSeconds () o similar. Compare: Colors.RED.getHex () con Colors.toHex (Colors.RED)

"como la capacidad de asignar fácilmente un cierto valor a un elemento enum"

enum EnumX{
  VAL_1(1),
  VAL_200(200);
  public final int certainValue;
  private X(int certainValue){this.certainValue = certainValue;}
}

"y, en consecuencia, la capacidad de convertir un número entero en una enumeración sin una cantidad decente de esfuerzo" Agregue un método que convierta int a enumeración que hace eso. Simplemente agregue HashMap estático <Integer, EnumX> que contiene la enumeración java de mapeo .

Si realmente desea convertir ord = VAL_200.ordinal () de nuevo a val_200 simplemente use: EnumX.values ​​() [ord]

mabn
fuente
3

Otra diferencia importante es que el compilador de Java trata los static finalcampos de tipos primitivos y String como literales. Significa que estas constantes se vuelven en línea. Es similar al C/C++ #definepreprocesador. Ver esta pregunta SO . Este no es el caso con las enumeraciones.

Seyed Jalal Hosseini
fuente
2

Obtiene la comprobación en tiempo de compilación de valores válidos cuando utiliza una enumeración. Mira esta pregunta.

zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
fuente
2

La mayor ventaja es que los Singleton de enum son fáciles de escribir y seguros para subprocesos:

public enum EasySingleton{
    INSTANCE;
}

y

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

ambos son similares y manejó la serialización por sí mismos mediante la implementación

//readResolve to prevent another instance of Singleton
    private Object readResolve(){
        return INSTANCE;
    }

más

Premraj
fuente
0

Creo que un enumno puede ser final, porque debajo del capó el compilador genera subclases para cada enumentrada.

Más información de la fuente

Sitansu
fuente
Internamente, no son finales porque, como usted dice, pueden subclasificarse internamente. Pero, por desgracia, no puede subclasificarlos por su cuenta, por ejemplo, para ampliarlo con sus propios valores.
glglgl
0

Hay muchas ventajas de las enumeraciones que se publican aquí, y estoy creando tales enumeraciones en este momento como se pregunta en la pregunta. Pero tengo una enumeración con 5-6 campos.

enum Planet{
EARTH(1000000, 312312321,31232131, "some text", "", 12),
....
other planets
....

En este tipo de casos, cuando tiene múltiples campos en enumeraciones, es mucho más difícil entender qué valor pertenece a cada campo, ya que necesita ver el constructor y el globo ocular.

La clase con static finalconstantes y el uso de Builderpatrones para crear dichos objetos lo hacen más legible. Pero, perdería todas las demás ventajas de usar una enumeración, si las necesita. Una desventaja de este tipo de clases es, es necesario agregar los Planetobjetos manualmente a la list/setdePlanets.

Yo prefiero enumeración sobre dicha clase, que values()viene muy bien y nunca se sabe si los necesita para su uso en switcho EnumSeto EnumMapen el futuro :)

Bikas Katwal
fuente
0

Razón principal: las enumeraciones lo ayudan a escribir código bien estructurado donde el significado semántico de los parámetros es claro y fuertemente tipado en tiempo de compilación, por todas las razones que otras respuestas han dado.

Quid pro quo: en Java, la variedad de miembros de Enum es definitiva. Eso normalmente es bueno, ya que ayuda a valorar la seguridad y las pruebas, pero en algunas situaciones podría ser un inconveniente, por ejemplo, si está extendiendo el código base existente quizás desde una biblioteca. Por el contrario, si los mismos datos están en una clase con campos estáticos, puede agregar fácilmente nuevas instancias de esa clase en tiempo de ejecución (es posible que también necesite escribir código para agregarlos a cualquier Iterable que tenga para esa clase). Pero este comportamiento de Enums se puede cambiar: usando la reflexión puede agregar nuevos miembros en tiempo de ejecución o reemplazar miembros existentes, aunque esto probablemente solo se debe hacer en situaciones especializadas donde no hay alternativa: es decir, es una solución extravagante y puede producir problemas inesperados, mira mi respuesta en¿Puedo agregar y eliminar elementos de enumeración en tiempo de ejecución en Java ?

Radfast
fuente