2012-02-13 21 views
50

Cuando uso banderas en Java, he visto dos enfoques principales. Uno usa valores int y una línea de sentencias if-else. La otra es usar enumeraciones y declaraciones de cambio de caso.Java: Enum vs. Int

Me preguntaba si había una diferencia en términos de uso de memoria y velocidad entre el uso de enums vs ints para flags.

+0

pregunta relacionada: http://stackoverflow.com/questions/4709175/why-and-what-for- java-enum/4709224 # 4709224 – sleske

Respuesta

112

Ambos ints y enums pueden usar tanto el interruptor como si-luego-else, y el uso de la memoria también es mínimo para ambos, y la velocidad es similar; no hay diferencias significativas entre ellos en los puntos que ha planteado.

Sin embargo, la diferencia más importante es la comprobación de tipo. Enums están marcados, ints no lo son.

Considere este código:

public class SomeClass { 
    public static int RED = 1; 
    public static int BLUE = 2; 
    public static int YELLOW = 3; 
    public static int GREEN = 3; // sic 

    private int color; 

    public void setColor(int color) { 
     this.color = color; 
    } 
} 

Mientras que muchos clientes utilizarán esto correctamente,

new SomeClass().setColor(SomeClass.RED); 

No hay nada que les impida escribir esto:

new SomeClass().setColor(999); 

Hay tres problemas con el uso del patrón public static final:

  • El problema se produce en tiempo de ejecución, no compilar tiempo, así que va a ser más caro para arreglar, y más difícil encontrar la causa
  • Tienes que escribir código para controlar el mal de entrada - típicamente una if-then-else con una final else throw new IllegalArgumentException("Unknown color " + color); - una vez más caros
  • No hay nada que impida una colisión de constantes - el código de la clase anterior se compilará a pesar de que YELLOW y GREEN ambos tienen el mismo valor 3

Si utiliza enums, que abordan todos estos problemas:

  • Su código no se compilará a menos que pase los valores válidos en
  • No hay necesidad de ningún código especial "mala entrada" - manejará el compilador eso para usted
  • valores de enumeración son únicos
+0

Bien juntos responden, y lo explica muy claramente. Gracias. – Kervvv

8

El uso de la memoria y la velocidad no son las consideraciones que importan. No podrías medir una diferencia de ninguna manera.

Creo que las enumeraciones deben ser preferidas cuando se aplican, porque enfatizan el hecho de que los valores elegidos van juntos y comprenden un conjunto cerrado. La legibilidad también se mejora mucho. El código que utiliza enums es más autodocumentado que los valores de int extraviados dispersos por todo el código.

Prefer enums.

+0

, sí, las enumeraciones están mucho más organizadas. – Jimmt

+1

Diría que el uso de 'int' proviene principalmente del hecho de que Java 1.4 no tenía' enum'. – skolima

+1

De acuerdo. Creo que eso es correcto Pero estamos hablando de cómo tomar una decisión sobre un nuevo desarrollo aquí. – duffymo

3

Tenga en cuenta que enums son seguros para el tipo, y no puede mezclar valores de una enumeración con otra. Esa es una buena razón para preferir enums sobre ints para banderas.

Por otro lado, si se utiliza ints para sus constantes, usted puede mezclar los valores de las constantes no relacionados, como esto:

public static final int SUNDAY = 1; 
public static final int JANUARY = 1; 

... 

// even though this works, it's a mistake: 
int firstMonth = SUNDAY; 

El uso de la memoria de enums sobre intses insignificante, y el tipo la seguridad enums proporciona hace que la sobrecarga mínima sea aceptable.

10

Usted puede incluso utilizar enumeraciones para sustituir a los bit a bit banderas combinadas como int flags = FLAG_1 | FLAG_2;

su lugar se puede utilizar un typesafe EnumSet:

Set<FlagEnum> flags = EnumSet.of(FlagEnum.FLAG_1, FlagEnum.FLAG_2); 

// then simply test with contains() 
if(flags.contains(FlagEnum.FLAG_1)) ... 

La documentación indica que esas clases se han optimizado internamente como vectores de bits y que la aplicación se debe realizar lo suficientemente bien como para reemplazar las banderas basadas en int.

+0

¿Esto se aplica a Android también? (Acerca de la eficacia) Los documentos dicen que es desde la versión 1.5, por lo que deberían ... –

4

Una de las razones por las que va a ver algo de código usando int banderas en lugar de un enum es que Java no tenía enumeraciones hasta Java 1.5

Así que si usted está buscando en código que fue escrito originalmente para una versión anterior de Java, entonces el patrón int era la única opción disponible.

Hay un número muy pequeño de lugares donde utilizar flags int es aún preferible en el código Java moderno, pero en la mayoría de los casos debería preferir usar enum, debido al tipo de seguridad y expresividad que ofrecen.

En términos de eficiencia, dependerá exactamente de cómo se usen. La JVM maneja ambos tipos de manera muy eficiente, pero el método int probablemente sea un poco más eficiente para algunos casos de uso (porque se manejan como primitivos en lugar de objetos), pero en otros casos, la enumeración sería más eficiente (porque no lo hace) Necesito tirar el boxeo/unboxing).

Será difícil encontrar una situación en la que la diferencia de eficiencia sea notable en una aplicación real, por lo que debe tomar una decisión basada en la calidad del código (legibilidad y seguridad) , lo que debería llevarlo a usar una enumeración el 99% del tiempo.

+2

Cabe señalar que el mismo efecto que 'enum' tiene en Java 5+ podría lograrse en versiones anteriores mediante * manualmente * creando una enumeración como clase (con un constructor privado, algunos campos 'estáticos finales 'y la correspondiente magia de serialización para garantizar la singularidad). Se usó * muy * rara vez, pero buscar en Google "enum seguro de tipo Java 1.4" probablemente mostrará un tutorial o dos. 'enum' es" solo "el azúcar sintáctico para eso. –

2

Responda a su pregunta: No, después de un tiempo insignificante para cargar la clase Enum, el rendimiento es el mismo.

Como han dicho otros, ambos tipos se pueden usar en instrucciones switch o if else. Además, como han indicado otros, debe favorecer los Enume sobre los int flags, ya que fueron diseñados para reemplazar ese patrón y brindan mayor seguridad.

SIN EMBARGO, hay un patrón mejor que usted considera. Proporcionando cualquier valor que se suponía que tu sentencia switch/if debía producir como propiedad.

Mire este enlace: http://docs.oracle.com/javase/1.5.0/docs/guide/language/enums.html Observe el patrón proporcionado para dar a los planetas masas y radios. Proporcionar la propiedad de esta manera asegura que no se olvide de cubrir un caso si agrega una enumeración.

+0

Las enumeraciones Java no son ints ... son clases orientadas a objetos. Hay una línea fina entre C enums y Java enums. – GGulati

+0

La parte sobre los ints es en realidad cómo implementan EnumSets. El hecho de que un Enum sea una clase completa se refleja en mi respuesta que alienta tratar a un Enum como una clase sobre el uso de declaraciones de cambio. – Joe

0

A pesar de que esta pregunta es viejo, me gustaría señalar lo que no se puede hacer con enteros

public interface AttributeProcessor { 
    public void process(AttributeLexer attributeLexer, char c); 
} 

public enum ParseArrayEnd implements AttributeProcessor { 
    State1{ 
     public void process(AttributeLexer attributeLexer, char c) { 
      .....}}, 
    State2{ 
     public void process(AttributeLexer attributeLexer, char c) { 
      .....}} 
} 

Y lo que puede hacer es hacer un mapa de lo que se espera valor como clave y la enumeración como valor,

Map<String, AttributeProcessor> map 
map.getOrDefault(key, ParseArrayEnd.State1).process(this, c); 
+0

Si bien es cierto esto no responde a la pregunta, que se refiere al rendimiento _runtime_ de las dos técnicas. – phs

2

me gusta usar enumeraciones cuando sea posible, pero tuve una situación en la que estaba teniendo para calcular millones de desplazamientos de archivo para los diferentes tipos de archivos que había definido en una enumeración y tuve para ejecutar una declaración de conmutación decenas de millones de veces para calcular la base de compensación en el tipo de enumeración . Me encontré con la siguiente prueba:

import java.util.Random; 

clase pública switchtest { enumeración pública MyEnum { valor1, valor2, Valor3, Valor4, valor5 };

public static void main(String[] args) 
{ 
    final String s1 = "Value1"; 
    final String s2 = "Value2"; 
    final String s3 = "Value3"; 
    final String s4 = "Value4"; 
    final String s5 = "Value5"; 

    String[] strings = new String[] 
    { 
     s1, s2, s3, s4, s5 
    }; 

    Random r = new Random(); 

    long l = 0; 

    long t1 = System.currentTimeMillis(); 

    for(int i = 0; i < 10_000_000; i++) 
    { 
     String s = strings[r.nextInt(5)]; 

     switch(s) 
     { 
      case s1: 
       // make sure the compiler can't optimize the switch out of existence by making the work of each case it does different 
       l = r.nextInt(5); 
       break; 
      case s2: 
       l = r.nextInt(10); 
       break; 
      case s3: 
       l = r.nextInt(15); 
       break; 
      case s4: 
       l = r.nextInt(20); 
       break; 
      case s5: 
       l = r.nextInt(25); 
       break; 
     } 
    } 

    long t2 = System.currentTimeMillis(); 

    for(int i = 0; i < 10_000_000; i++) 
    { 
     MyEnum e = MyEnum.values()[r.nextInt(5)]; 

     switch(e) 
     { 
      case Value1: 
       // make sure the compiler can't optimize the switch out of existence by making the work of each case it does different 
       l = r.nextInt(5); 
       break; 
      case Value2: 
       l = r.nextInt(10); 
       break; 
      case Value3: 
       l = r.nextInt(15); 
       break; 
      case Value4: 
       l = r.nextInt(20); 
       break; 
      case Value5: 
       l = r.nextInt(25); 
       break; 
     } 
    } 

    long t3 = System.currentTimeMillis(); 

    for(int i = 0; i < 10_000_000; i++) 
    { 
     int xx = r.nextInt(5); 

     switch(xx) 
     { 
      case 1: 
       // make sure the compiler can't optimize the switch out of existence by making the work of each case it does different 
       l = r.nextInt(5); 
       break; 
      case 2: 
       l = r.nextInt(10); 
       break; 
      case 3: 
       l = r.nextInt(15); 
       break; 
      case 4: 
       l = r.nextInt(20); 
       break; 
      case 5: 
       l = r.nextInt(25); 
       break; 
     } 
    } 

    long t4 = System.currentTimeMillis(); 

    System.out.println("strings:" + (t2 - t1)); 
    System.out.println("enums :" + (t3 - t2)); 
    System.out.println("ints :" + (t4 - t3)); 
} 

}

y obtuvo los siguientes resultados:

cadenas: 442

enumeraciones: 455

Ints: 362

Así que desde este decidí que para mis enumeraciones fueron lo suficientemente eficientes. Cuando disminuí el recuento de lazos a 1M de 10M, la cadena y las enumeraciones tardaron aproximadamente el doble que la int que indica que hubo cierta sobrecarga para usar cadenas y enumeraciones por primera vez en comparación con las entradas.

+0

MyEnum.values ​​() en su muestra hace una copia de matriz en cada iteración. No demasiado eficiente ... –

2

Sí, hay una diferencia. Bajo la moderna java de 64 bits, los valores Enum son esencialmente punteros a los objetos y toman 64 bits (operaciones no comprimidas) o usan CPU adicionales (operaciones comprimidas).

Mi prueba mostró un 10% de degradación del rendimiento para las enumeraciones (1.8u25, AMD FX-4100): 13k 14k vs ns ns

fuente

Prueba a continuación:

public class Test { 

    public static enum Enum { 
     ONE, TWO, THREE 
    } 

    static class CEnum { 
     public Enum e; 
    } 

    static class CInt { 
     public int i; 
    } 

    public static void main(String[] args) { 
     CEnum[] enums = new CEnum[8192]; 
     CInt[] ints = new CInt[8192]; 

     for (int i = 0 ; i < 8192 ; i++) { 
      enums[i] = new CEnum(); 
      ints[i] = new CInt(); 
      ints[i].i = 1 + (i % 3); 
      if (i % 3 == 0) { 
       enums[i].e = Enum.ONE; 
      } else if (i % 3 == 1) { 
       enums[i].e = Enum.TWO; 
      } else { 
       enums[i].e = Enum.THREE; 
      } 
     } 
     int k=0; //calculate something to prevent tests to be optimized out 

     k+=test1(enums); 
     k+=test1(enums); 
     k+=test1(enums); 
     k+=test1(enums); 
     k+=test1(enums); 
     k+=test1(enums); 
     k+=test1(enums); 
     k+=test1(enums); 
     k+=test1(enums); 
     k+=test1(enums); 

     System.out.println(); 

     k+=test2(ints); 
     k+=test2(ints); 
     k+=test2(ints); 
     k+=test2(ints); 
     k+=test2(ints); 
     k+=test2(ints); 
     k+=test2(ints); 
     k+=test2(ints); 
     k+=test2(ints); 
     k+=test2(ints); 

     System.out.println(k); 



    } 

    private static int test2(CInt[] ints) { 
     long t; 
     int k = 0; 
     for (int i = 0 ; i < 1000 ; i++) { 
      k+=test(ints); 
     } 

     t = System.nanoTime(); 
     k+=test(ints); 
     System.out.println((System.nanoTime() - t)/100 + "ns"); 
     return k; 
    } 

    private static int test1(CEnum[] enums) { 
     int k = 0; 
     for (int i = 0 ; i < 1000 ; i++) { 
      k+=test(enums); 
     } 

     long t = System.nanoTime(); 
     k+=test(enums); 
     System.out.println((System.nanoTime() - t)/100 + "ns"); 
     return k; 
    } 

    private static int test(CEnum[] enums) { 
     int i1 = 0; 
     int i2 = 0; 
     int i3 = 0; 

     for (int j = 100 ; j != 0 ; --j) 
     for (int i = 0 ; i < 8192 ; i++) { 
      CEnum c = enums[i]; 
      if (c.e == Enum.ONE) { 
       i1++; 
      } else if (c.e == Enum.TWO) { 
       i2++; 
      } else { 
       i3++; 
      } 
     } 

     return i1 + i2*2 + i3*3; 
    } 

    private static int test(CInt[] enums) { 
     int i1 = 0; 
     int i2 = 0; 
     int i3 = 0; 

     for (int j = 100 ; j != 0 ; --j) 
     for (int i = 0 ; i < 8192 ; i++) { 
      CInt c = enums[i]; 
      if (c.i == 1) { 
       i1++; 
      } else if (c.i == 2) { 
       i2++; 
      } else { 
       i3++; 
      } 
     } 

     return i1 + i2*2 + i3*3; 
    } 
}