Dois-je strictement éviter d'utiliser les énumérations sur Android?

93

J'avais l'habitude de définir un ensemble de constantes liées comme des Bundleclés ensemble dans une interface comme ci-dessous:

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

Cela me fournit un moyen plus agréable de regrouper les constantes associées et de les utiliser en effectuant une importation statique (pas des implémentations). Je sais que le Androidcadre utilise aussi les constantes de même que Toast.LENTH_LONG, View.GONE.

Cependant, je pense souvent que le Java Enumsfournit un moyen bien meilleur et puissant de représenter la constante.

Mais est - il un problème de performence à utiliser enumssur Android?

Avec un peu de recherche, j'ai fini dans la confusion. De cette question ? « Évitez les énumérations où vous avez besoin Ints » retirés des conseils de performance Android il est clair que Googlea supprimé « Évitez énumérations » de ses conseils de performance, mais à partir de son docs de formation officiels Soyez au courant des frais généraux mémoire section , il dit clairement: « énumérations nécessitent souvent plus de deux fois plus de mémoire que les constantes statiques. Vous devez strictement éviter d'utiliser les énumérations sur Android. " Est-ce que cela est toujours valable? (Par exemple, dans les Javaversions après 1.6)

Un autre problème que j'ai observé est de les envoyer enumsen intentsutilisant Bundleje devrais les envoyer par sérialisation (c'est putSerializable()-à- dire que je pense qu'une opération coûteuse par rapport à la putString()méthode primitive , même si enumselle la fournit gratuitement).

Quelqu'un peut-il clarifier quelle est la meilleure façon de représenter la même chose Android? Dois - je éviter strictement l' utilisation enumssur Android?

nvinayshetty
la source
5
Vous devez utiliser les outils dont vous disposez. En fait, une activité ou un fragment utilise beaucoup de mémoire et d'utilisation du processeur, mais ce n'est pas une raison pour arrêter de les utiliser. Utilisez un int statique si vous n'avez besoin que de cela, et utilisez des énumérations lorsque vous en avez besoin.
Patrick
11
Je suis d'accord. Cela sent l'optimisation prématurée. À moins que vous ayez un problème de performances et / ou de mémoire et que vous puissiez prouver par le profilage que les énumérations en sont la cause, utilisez-les là où cela a du sens.
GreyBeardedGeek
2
On croyait autrefois que les énumérations entraînaient une pénalité de performance non trival, mais les benchmarks plus récents ne montrent aucun avantage à utiliser des constantes à la place. Voir stackoverflow.com/questions/24491160/… ainsi que stackoverflow.com/questions/5143256/…
Benjamin Sergent
1
Pour éviter les pénalités de performances liées à la sérialisation d'un Enum dans un Bundle, vous pouvez le passer en tant qu'int en utilisant à la Enum.ordinal()place.
BladeCoder
1
enfin il y a quelques explications sur les problèmes de performances sur Enum ici youtube.com/watch?v=Hzs6OBcvNQE
nvinayshetty

Réponses:

116

À utiliser enumlorsque vous avez besoin de ses fonctionnalités. Ne l'évitez pas strictement .

Java enum est plus puissant, mais si vous n'avez pas besoin de ses fonctionnalités, utilisez des constantes, elles occupent moins d'espace et elles peuvent être primitives elles-mêmes.

Quand utiliser enum:

  • vérification de type - vous ne pouvez accepter que les valeurs répertoriées, et elles ne sont pas continues (voir ci-dessous ce que j'appelle continu ici)
  • surcharge de méthode - chaque constante enum a sa propre implémentation d'une méthode

    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);
    }
  • plus de données - votre seule constante contient plus d'une information qui ne peut pas être placée dans une seule variable

  • données compliquées - vos méthodes de besoin constant pour opérer sur les données

Quand ne pas utiliser enum:

  • vous pouvez accepter toutes les valeurs d'un même type, et vos constantes ne contiennent que les plus utilisées
  • vous pouvez accepter des données continues

    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);
            }
    
            ...
        }
    }
  • pour les noms (comme dans votre exemple)
  • pour tout le reste qui n'a pas vraiment besoin d'une énumération

Les énumérations occupent plus d'espace

  • une seule référence à une constante d'énumération occupe 4 octets
  • chaque constante d'énumération occupe un espace qui est une somme des tailles de ses champs alignées sur 8 octets + la surcharge de l'objet
  • la classe enum elle-même occupe un certain espace

Les constantes occupent moins d'espace

  • une constante n'a pas de référence donc c'est une donnée pure (même si c'est une référence, alors l'instance enum serait une référence à une autre référence)
  • des constantes peuvent être ajoutées à une classe existante - il n'est pas nécessaire d'ajouter une autre classe
  • les constantes peuvent être en ligne; il apporte des fonctionnalités étendues au moment de la compilation (telles que la vérification des null, la recherche de code mort, etc.)
Kamil Jarosz
la source
2
Vous pouvez utiliser l'annotation @IntDef pour simuler la vérification de type des constantes int. C'est un argument de moins en faveur des énumérations Java.
BladeCoder
3
@BladeCoder non, vous n'avez pas besoin d'une référence à la constante
Kamil Jarosz
1
En outre, il est bon de noter que l'utilisation d'énumérations favorise l'utilisation de la réflexion. Et on sait que l'utilisation de la réflexion est un énorme succès pour Android. Voir ici: blog.nimbledroid.com/2016/02/23/slow-Android-reflection.html
w3bshark
3
@ w3bshark Comment les énumérations encouragent-elles l'utilisation de la réflexion?
Kevin Krumwiede
3
Une autre chose à garder à l'esprit est qu'un vidage de tas du vide standard "Hello, world!" Le projet comprend plus de 4 000 classes et 700 000 objets. Même si vous aviez une énumération ridiculement énorme avec des milliers de constantes, vous pouvez être sûr que l'effet sur les performances serait négligeable à côté du gonflement du framework Android lui-même.
Kevin Krumwiede
57

Si les énumérations ont simplement des valeurs, vous devriez essayer d'utiliser IntDef / StringDef, comme indiqué ici:

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

Exemple: au lieu de:

enum NavigationMode {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS} 

tu utilises:

@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;

et dans la fonction qui l'a comme paramètre / valeur renvoyée, utilisez:

@NavigationMode
public abstract int getNavigationMode();

public abstract void setNavigationMode(@NavigationMode int mode);

Si l'énumération est complexe, utilisez une énumération. C'est pas si mal.

Pour comparer les énumérations et les valeurs constantes, vous devriez lire ici:

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

Leur exemple est une énumération avec 2 valeurs. Il faut 1112 octets dans le fichier dex contre 128 octets lorsque des entiers constants sont utilisés. Cela a du sens, car les énumérations sont de vraies classes, par opposition à la façon dont cela fonctionne sur C / C ++.

développeur android
la source
Bien que j'apprécie les autres réponses fournissant des avantages / inconvénients des énumérations, cette réponse devrait être la réponse acceptée car elle fournit la meilleure solution pour Android, en particulier. Les annotations de support sont la solution. En tant que développeurs Android, nous devrions penser "à moins que j'aie une bonne raison d'utiliser les énumérations, j'utiliserai des constantes avec des annotations", plutôt que la façon de penser des autres réponses "à moins que je ne commence à m'inquiéter de la mémoire / des performances plus tard, Je peux utiliser des énumérations ". Arrêtez-vous maintenant d'avoir des problèmes de performances plus tard!
w3bshark
3
@ w3bshark Si vous rencontrez des problèmes de performances, il est peu probable que les énumérations soient la première chose à laquelle vous devriez penser pour les résoudre. Les annotations ont leurs propres compromis: elles ne prennent pas en charge le compilateur, elles ne peuvent pas avoir leurs propres champs et méthodes, elles sont fastidieuses à écrire, vous pouvez facilement oublier d'annoter un int, et ainsi de suite. Ils ne sont pas une meilleure solution dans l'ensemble, ils sont juste là pour économiser de l'espace sur le tas lorsque vous en avez besoin.
Malcolm
1
@androiddeveloper Vous ne pouvez pas faire d'erreur en passant une constante non définie dans la déclaration enum. Ce code ne se compilera jamais. Si vous passez une mauvaise constante avec une IntDefannotation, ce sera le cas. Quant aux montres, je pense que c'est assez clair aussi. Quel appareil a plus de puissance CPU, de RAM et de batterie: un téléphone ou une montre? Et lesquels ont besoin d'un logiciel pour être plus optimisé pour les performances à cause de cela?
Malcolm
3
@androiddeveloper OK, si vous insistez sur une terminologie stricte, je voulais dire par erreurs des erreurs qui ne sont pas des erreurs de compilation (erreurs d'exécution ou de logique de programme). Êtes-vous en train de me troller ou ne comprenez-vous vraiment pas la différence entre eux et les avantages des erreurs de compilation? C'est un sujet bien discuté, recherchez-le sur le Web. En ce qui concerne les montres, Android inclut plus d'appareils, mais les plus contraints seront le plus petit dénominateur commun.
Malcolm
2
@androiddeveloper J'ai déjà répondu à ces deux déclarations, les répéter une fois de plus n'invalide pas ce que j'ai dit.
Malcolm le
12

En plus des réponses précédentes, j'ajouterais que si vous utilisez Proguard (et que vous devez absolument le faire pour réduire la taille et obscurcir votre code), alors votre Enumssera automatiquement converti @IntDefpartout où c'est possible:

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

classe / unboxing / enum

Simplifie les types enum en constantes entières, dans la mesure du possible.

Par conséquent, si vous avez des valeurs discrètes et qu'une méthode devrait permettre de ne prendre que ces valeurs et pas d'autres du même type, alors j'utiliserais Enum, car Proguard fera ce travail manuel d'optimisation du code pour moi.

Et voici un bon article sur l'utilisation des énumérations de Jake Wharton, jetez-y un œil.

En tant que développeur de bibliothèque, je reconnais ces petites optimisations qui doivent être effectuées car nous voulons avoir le moins d'impact possible sur la taille, la mémoire et les performances de l'application consommatrice. Mais il est important de réaliser que [...] mettre une énumération dans votre API publique par rapport à des valeurs entières le cas échéant est parfaitement [...] bien. Connaître la différence pour prendre des décisions éclairées est ce qui compte

Gaket
la source
11

Dois-je strictement éviter d'utiliser les énumérations sur Android?

Non. " Strictement " signifie qu'ils sont si mauvais qu'ils ne devraient pas du tout être utilisés. Il est possible que des problèmes de performances surviennent dans une situation extrême comme de nombreuses opérations (des milliers ou des millions) avec des énumérations (consécutives sur le thread d'interface utilisateur). Les opérations d'E / S réseau qui doivent strictement se produire dans un thread d'arrière-plan sont beaucoup plus courantes . L'utilisation la plus courante des énumérations est probablement une sorte de vérification de type - si un objet est ceci ou cela qui est si rapide, vous ne pourrez pas remarquer une différence entre une seule comparaison d'énumérations et une comparaison d'entiers.

Quelqu'un peut-il clarifier quelle est la meilleure façon de représenter la même chose dans Android?

Il n'y a pas de règle générale à ce sujet. Utilisez tout ce qui fonctionne pour vous et vous aide à préparer votre application. Optimisez plus tard - après avoir remarqué un goulot d'étranglement qui ralentit certains aspects de votre application.

stan0
la source
1
Pourquoi optimiseriez-vous plus tard, alors qu'il est tout aussi simple d'utiliser des constantes avec des annotations de support et d'optimiser maintenant? Voir la réponse de @ android_developer ici.
w3bshark
4

Deux faits.

1, Enum est l'une des fonctionnalités les plus puissantes de JAVA.

2, le téléphone Android a généralement beaucoup de mémoire.

Donc ma réponse est NON. J'utiliserai Enum sous Android.

Kai Wang
la source
2
Si Android a BEAUCOUP de mémoire, cela ne signifie pas que votre application doit tout consommer et n'en laisser aucune pour les autres. Vous devez suivre les meilleures pratiques afin d'éviter que votre application ne soit tuée par Android OS. Je ne dis pas de ne pas utiliser d'énumérations, mais de n'utiliser que lorsque vous n'avez pas d'alternative.
Varundroid
1
Je suis d'accord avec vous pour dire que nous devrions suivre les meilleures pratiques, cependant, la meilleure pratique ne signifie pas toujours utiliser le moins de mémoire. Garder le code propre et facile à comprendre est bien plus important que de sauvegarder plusieurs k de mémoire. Vous devez trouver un équilibre.
Kai Wang
1
Je suis d'accord avec vous pour dire que nous devons trouver un équilibre, mais l'utilisation de TypeDef ne rendra pas notre code mauvais ou moins maintenable. Je l'utilise depuis plus d'un an maintenant et je n'ai jamais eu l'impression que mon code n'était pas facile à comprendre, et aucun de mes pairs ne s'est jamais plaint que les TypeDefs sont difficiles à comprendre par rapport aux enums. Mon conseil est de n'utiliser les énumérations que lorsque vous n'avez pas d'autre option, mais de l'éviter si vous le pouvez.
Varundroid
2

J'aime ajouter que vous ne pouvez pas utiliser @Annotations lorsque vous déclarez une liste <> ou une carte <> où la clé ou la valeur est de l'une de vos interfaces d'annotation. Vous obtenez l'erreur "Les annotations ne sont pas autorisées ici".

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 ***

Donc, lorsque vous avez besoin de le regrouper dans une liste / carte, utilisez enum, car ils peuvent être ajoutés, mais les groupes @annotated int / string ne le peuvent pas.

Grisgram
la source