¿Debo evitar estrictamente el uso de enumeraciones en Android?

93

Solía ​​definir un conjunto de constantes relacionadas como Bundleclaves juntas en una interfaz como la siguiente:

public interface From{
    String LOGIN_SCREEN = "LoginSCreen";
    String NOTIFICATION = "Notification";
    String WIDGET = "widget";
}

Esto me proporciona una forma más agradable de agrupar constantes relacionadas y usarlas haciendo una importación estática (no implementaciones). Sé Androidmarco también utiliza las constantes en la misma manera como Toast.LENTH_LONG, View.GONE.

Sin embargo, a menudo siento que Java Enumsproporcionan una forma mucho mejor y poderosa de representar la constante.

Pero hay un problema performence en el uso enumsde Android?

Con un poco de investigación terminé confundido. A partir de esta pregunta "Evitar enumeraciones donde solo necesita Ints" se eliminó de las sugerencias de rendimiento de Android, queda claro que Googlese eliminó "Evitar enumeraciones" de sus sugerencias de rendimiento, pero de sus documentos de capacitación oficiales Tenga en cuenta la sección de sobrecarga de memoria que dice claramente: "Enums a menudo requieren más del doble de memoria que las constantes estáticas. Debes evitar estrictamente el uso de enumeraciones en Android. " ¿Sigue siendo válido? (Por ejemplo, en Javaversiones posteriores a la 1.6).

Un problema más que observé es enviar a enumstravés del intentsuso Bundle. Debería enviarlos serializando (es decir putSerializable(), creo que es una operación costosa en comparación con el putString()método primitivo , aunque lo enumsproporciona de forma gratuita).

¿Alguien puede aclarar cuál es la mejor manera de representar lo mismo Android? ¿Debo estrictamente evitar el uso enumsde Android?

nvinayshetty
fuente
5
Debe utilizar las herramientas que tiene disponibles. De hecho, una actividad o un fragmento ocupa mucha memoria y uso de la CPU, pero esa no es razón para dejar de usarlos. Use un int estático si solo lo necesita, y use enums cuando los necesite.
Patrick
11
Estoy de acuerdo. Esto huele a optimización prematura. A menos que tenga un problema de rendimiento o de memoria, y pueda probar mediante la creación de perfiles que las enumeraciones son la causa, úselas donde tenga sentido.
GreyBeardedGeek
2
Se solía creer que las enumeraciones incurrían en una penalización de rendimiento no trival, pero los puntos de referencia más recientes no muestran ningún beneficio al usar constantes en su lugar. Ver stackoverflow.com/questions/24491160/… así como stackoverflow.com/questions/5143256/…
Benjamin Sergent
1
Para evitar la penalización de rendimiento de serializar un Enum en un Bundle, puede pasarlo como un int usando en su Enum.ordinal()lugar.
BladeCoder
1
finalmente hay algunas explicaciones sobre los problemas de rendimiento en Enum aquí youtube.com/watch?v=Hzs6OBcvNQE
nvinayshetty

Respuestas:

116

Úselo enumcuando necesite sus características. No lo evites estrictamente .

La enumeración de Java es más poderosa, pero si no necesita sus características, use constantes, ocupan menos espacio y pueden ser primitivas en sí mismas.

Cuándo usar la enumeración:

  • verificación de tipo: puede aceptar solo los valores enumerados y no son continuos (vea a continuación lo que llamo continuo aquí)
  • sobrecarga de métodos: cada constante de enumeración tiene su propia implementación de un método

    public enum UnitConverter{
        METERS{
            @Override
            public double toMiles(final double meters){
                return meters * 0.00062137D;
            }
    
            @Override
            public double toMeters(final double meters){
                return meters;
            }
        },
        MILES{
            @Override
            public double toMiles(final double miles){
                return miles;
            }
    
            @Override
            public double toMeters(final double miles){
                return miles / 0.00062137D;
            }
        };
    
        public abstract double toMiles(double unit);
        public abstract double toMeters(double unit);
    }
    
  • más datos: su única constante contiene más de una información que no se puede poner en una variable

  • datos complicados: sus métodos de necesidad constante para operar con los datos

Cuando no usar enum:

  • puede aceptar todos los valores de un tipo, y sus constantes contienen solo los más utilizados
  • puedes aceptar datos continuos

    public class Month{
        public static final int JANUARY = 1;
        public static final int FEBRUARY = 2;
        public static final int MARCH = 3;
        ...
    
        public static String getName(final int month){
            if(month <= 0 || month > 12){
                throw new IllegalArgumentException("Invalid month number: " + month);
            }
    
            ...
        }
    }
    
  • para nombres (como en tu ejemplo)
  • para todo lo demás que realmente no necesita una enumeración

Las enumeraciones ocupan más espacio

  • una sola referencia a una constante de enumeración ocupa 4 bytes
  • cada constante de enumeración ocupa un espacio que es una suma de los tamaños de sus campos alineados a 8 bytes + sobrecarga del objeto
  • la clase enum en sí ocupa algo de espacio

Las constantes ocupan menos espacio

  • una constante no tiene una referencia, por lo que es un dato puro (incluso si es una referencia, la instancia de enum sería una referencia a otra referencia)
  • las constantes se pueden agregar a una clase existente; no es necesario agregar otra clase
  • las constantes pueden estar en línea; trae características extendidas en tiempo de compilación (como verificación nula, búsqueda de código muerto, etc.)
Kamil Jarosz
fuente
2
Puede usar la anotación @IntDef para simular la verificación de tipos de constantes int. Es un argumento menos a favor de Java Enums.
BladeCoder
3
@BladeCoder no, no necesitas una referencia a la constante
Kamil Jarosz
1
Además, es bueno tener en cuenta que el uso de enumeraciones promueve el uso de la reflexión. Y se sabe que el uso de la reflexión es un gran impacto en el rendimiento de Android. Vea aquí: blog.nimbledroid.com/2016/02/23/slow-Android-reflection.html
w3bshark
3
@ w3bshark ¿Cómo promueven las enumeraciones el uso de la reflexión?
Kevin Krumwiede
3
Otra cosa a tener en cuenta es que un volcado de pila del estándar vacío "¡Hola, mundo!" El proyecto incluye más de 4.000 clases y 700.000 objetos. Incluso si tuvieras una enumeración ridículamente enorme con miles de constantes, puedes estar seguro de que el efecto de rendimiento sería insignificante junto con la hinchazón del marco de Android en sí.
Kevin Krumwiede
57

Si las enumeraciones simplemente tienen valores, debe intentar usar IntDef / StringDef, como se muestra aquí:

https://developer.android.com/studio/write/annotations.html#enum-annotations

Ejemplo: en lugar de:

enum NavigationMode {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS} 

tu usas:

@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}

public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;

y en la función que lo tiene como parámetro / valor devuelto, use:

@NavigationMode
public abstract int getNavigationMode();

public abstract void setNavigationMode(@NavigationMode int mode);

En caso de que la enumeración sea compleja, use una enumeración. No está tan mal.

Para comparar enumeraciones frente a valores constantes, debe leer aquí:

http://hsc.com/Blog/Best-Practices-For-Memory-Optimization-on-Android-1

Su ejemplo es de una enumeración con 2 valores. Se necesitan 1112 bytes en un archivo dex en comparación con 128 bytes cuando se utilizan enteros constantes. Tiene sentido, ya que las enumeraciones son clases reales, a diferencia de cómo funciona en C / C ++.

desarrollador de Android
fuente
Aunque aprecio las otras respuestas que brindan ventajas y desventajas de las enumeraciones, esta respuesta debería ser la respuesta aceptada, ya que proporciona la mejor solución para Android, específicamente. Las anotaciones de soporte son el camino a seguir. Nosotros, como desarrolladores de Android, deberíamos pensar "a menos que tenga una buena razón para usar enumeraciones, usaré constantes con anotaciones", en lugar de la forma de pensar de las otras respuestas "a menos que empiece a preocuparme por la memoria / rendimiento más adelante, Puedo usar enumeraciones ". ¡Evite tener problemas de rendimiento más adelante!
w3bshark
3
@ w3bshark Si tiene problemas de rendimiento, es poco probable que las enumeraciones sean lo primero en lo que debería pensar para resolverlos. Las anotaciones tienen sus propias ventajas y desventajas: no tienen compatibilidad con el compilador, no pueden tener sus propios campos y métodos, son tediosas de escribir, puede olvidarse fácilmente de anotar un int, etc. En general, no son una mejor solución, solo están ahí para ahorrar espacio en el montón cuando lo necesite.
Malcolm
1
@androiddeveloper No puede cometer un error al pasar una constante no definida en la declaración enum. Este código nunca se compilará. Si pasa una constante incorrecta con una IntDefanotación, lo hará. En cuanto a los relojes, creo que también está bastante claro. ¿Qué dispositivo tiene más potencia de CPU, RAM y batería: un teléfono o un reloj? ¿Y cuáles necesitan que el software esté más optimizado para el rendimiento debido a eso?
Malcolm
3
@androiddeveloper Está bien, si insiste en una terminología estricta, por errores me refiero a errores que no son errores de tiempo de compilación (errores de tiempo de ejecución o de lógica de programa). ¿Me estás trolleando o realmente no entiendes la diferencia entre ellos y los beneficios de los errores en tiempo de compilación? Este es un tema muy discutido, búsquelo en la web. En cuanto a relojes, Android incluye más dispositivos, pero los más restringidos serán el mínimo común denominador.
Malcolm
2
@androiddeveloper Ya respondí a ambas declaraciones, repetirlas una vez más no invalida lo que dije.
Malcolm
12

Además de las respuestas anteriores, agregaría que si está utilizando Proguard (y definitivamente debería hacerlo para reducir el tamaño y ofuscar su código), entonces Enumsse convertirá automáticamente a @IntDefdonde sea posible:

https://www.guardsquare.com/en/proguard/manual/optimizations

class / unboxing / enum

Simplifica los tipos de enumeración a constantes enteras, siempre que sea posible.

Por lo tanto, si tiene algunos valores discretos y algún método debería permitir tomar solo estos valores y no otros del mismo tipo, entonces usaría Enum, porque Proguard hará que este manual funcione de optimización de código para mí.

Y aquí hay una buena publicación sobre el uso de enumeraciones de Jake Wharton, échale un vistazo.

Como desarrollador de bibliotecas, reconozco estas pequeñas optimizaciones que deben realizarse, ya que queremos tener el menor impacto posible en el tamaño, la memoria y el rendimiento de la aplicación que consume. Pero es importante darse cuenta de que poner una [...] enumeración en su API pública frente a valores enteros donde sea apropiado está perfectamente bien. Conocer la diferencia para tomar decisiones informadas es lo importante

Gaket
fuente
11

¿Debo evitar estrictamente el uso de enumeraciones en Android?

No. " Estrictamente " significa que son tan malos que no deben usarse en absoluto. Es posible que surjan problemas de rendimiento en una situación extrema como muchas muchas (miles o millones de) operaciones con enumeraciones (consecutivas en el hilo de la interfaz de usuario). Mucho más comunes son las operaciones de E / S de red que deberían ocurrir estrictamente en un hilo de fondo. El uso más común de enumeraciones es probablemente algún tipo de verificación de tipo: si un objeto es esto o aquello que es tan rápido, no podrá notar una diferencia entre una sola comparación de enumeraciones y una comparación de enteros.

¿Alguien puede aclarar cuál es la mejor manera de representar lo mismo en Android?

No existe una regla general para esto. Utilice lo que funcione para usted y le ayude a preparar su aplicación. Optimice más tarde, después de que note que hay un cuello de botella que ralentiza algún aspecto de su aplicación.

stan0
fuente
1
¿Por qué optimizaría más tarde, cuando es tan fácil de usar constantes con anotaciones de soporte como optimizar ahora? Vea la respuesta de @ android_developer aquí.
w3bshark
4

Dos hechos.

1, Enum es una de las funciones más poderosas de JAVA.

2, el teléfono Android suele tener MUCHA memoria.

Entonces mi respuesta es NO. Usaré Enum en Android.

Kai Wang
fuente
2
Si Android tiene MUCHA memoria, eso no significa que su aplicación deba consumirla toda y no dejar ninguna para los demás. Debe seguir las mejores prácticas para evitar que su aplicación sea eliminada por el sistema operativo Android. No estoy diciendo que no use enumeraciones, sino que solo use cuando no tenga otra alternativa.
Varundroid
1
Estoy de acuerdo con usted en que debemos seguir las mejores prácticas, sin embargo, las mejores prácticas no siempre significan usar la menor cantidad de memoria. Mantener el código limpio y fácil de entender es mucho más importante que guardar varios k de memoria. Necesitas encontrar un equilibrio.
Kai Wang
1
Estoy de acuerdo contigo en que necesitamos encontrar un equilibrio, pero usar TypeDef no hará que nuestro código se vea mal o sea menos fácil de mantener. Lo estoy usando durante más de un año y nunca sentí que mi código no fuera fácil de entender, además, ninguno de mis compañeros se quejó de que TypeDefs es difícil de entender en comparación con las enumeraciones. Mi consejo es usar enumeraciones solo cuando no tenga otra opción, pero evítelo si puede.
Varundroid
2

Me gusta agregar que no puede usar @Annotations cuando declara una List <> o Map <> donde la clave o el valor es de una de sus interfaces de anotación. Aparece el error "No se permiten anotaciones aquí".

enum Values { One, Two, Three }
Map<String, Values> myMap;    // This works

// ... but ...
public static final int ONE = 1;
public static final int TWO = 2;
public static final int THREE = 3;

@Retention(RetentionPolicy.SOURCE)
@IntDef({ONE, TWO, THREE})
public @interface Values {}

Map<String, @Values Integer> myMap;    // *** ERROR ***

Entonces, cuando necesite empaquetarlo en una lista / mapa, use enum, ya que se pueden agregar, pero @annotated int / string groups no.

Grisgram
fuente