Czy powinienem bezwzględnie unikać używania wyliczeń w systemie Android?

93

Kiedyś definiowałem zestaw powiązanych stałych, takich jak Bundleklucze razem w interfejsie jak poniżej:

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

Zapewnia mi to przyjemniejszy sposób grupowania powiązanych stałych razem i używania ich przez import statyczny (nie implementuje). Wiem Androidramy stosowane są także stałe w taki sam sposób jak Toast.LENTH_LONG, View.GONE.

Jednak często czuję, że Java Enumszapewniają znacznie lepszy i potężniejszy sposób przedstawiania stałej.

Ale czy istnieje problem wydajno w użyciu enumsna Android?

Po krótkich badaniach znalazłem się w zamieszaniu. Z tego pytania „Unikaj wyliczeń tam, gdzie potrzebujesz tylko Ints” zostało usunięte ze wskazówek dotyczących wydajności Androida? Jasne jest, że Googleusunięto „Unikaj wyliczeń” ze wskazówek dotyczących wydajności, ale z oficjalnych dokumentów szkoleniowych Należy pamiętać o sekcji poświęconej pamięci, która wyraźnie mówi: „Wyliczenia często wymagają więcej niż dwa razy więcej pamięci niż stałe statyczne. Należy bezwzględnie unikać wyliczeń w systemie Android. „ Czy to nadal jest dobre? (Powiedzmy w Javawersjach po 1.6)

Jeszcze jedna kwestia, że obserwowana jest przesłanie enumscałej intentsużyciu Bundlepowinienem wysłać je przez szeregowania (czyli putSerializable(), że myślę, że operacja kosztowna w porównaniu do pierwotnej putString()metodzie, mimo iż enumsstanowi to za darmo).

Czy ktoś może wyjaśnić, który z nich jest najlepszy do reprezentowania tego samego w Android? Czy powinienem unikać używania enumsna Android?

nvinayshetty
źródło
5
Powinieneś użyć dostępnych narzędzi. W rzeczywistości aktywność lub fragment zajmuje dużo pamięci i wykorzystania procesora, ale to nie jest powód, aby przestać ich używać. Użyj static int, jeśli tylko tego potrzebujesz, i używaj wyliczeń, gdy ich potrzebujesz.
Patrick
11
Zgadzam się. To pachnie jak przedwczesna optymalizacja. O ile nie masz problemów z wydajnością i / lub pamięcią i nie możesz udowodnić poprzez profilowanie, że przyczyną są wyliczenia, używaj ich tam, gdzie ma to sens.
GreyBeardedGeek
2
Kiedyś uważano, że wyliczenia wiązały się z mniejszym niż trójwartościowy spadkiem wydajności, ale nowsze testy porównawcze nie wykazują żadnych korzyści z używania zamiast nich stałych. Zobacz stackoverflow.com/questions/24491160/… oraz stackoverflow.com/questions/5143256/…
Benjamin Sergent
1
Aby uniknąć spadku wydajności wynikającego z serializacji wyliczenia w pakiecie, można Enum.ordinal()zamiast tego przekazać go jako int używając .
BladeCoder,
1
na koniec jest kilka wyjaśnień dotyczących problemów z wydajnością w Enum tutaj youtube.com/watch?v=Hzs6OBcvNQE
nvinayshetty

Odpowiedzi:

116

Używaj, enumgdy potrzebujesz jego funkcji. Nie unikaj tego ściśle .

Wyliczenie w Javie jest potężniejsze, ale jeśli nie potrzebujesz jego funkcji, użyj stałych, zajmują one mniej miejsca i same mogą być prymitywne.

Kiedy używać wyliczenia:

  • sprawdzanie typów - możesz akceptować tylko wymienione wartości i nie są one ciągłe (zobacz poniżej to, co nazywam tutaj ciągłym )
  • przeciążanie metody - każda stała wyliczeniowa ma swoją własną implementację metody

    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);
    }
    
  • więcej danych - Twoja jedna stała zawiera więcej niż jedną informację, której nie można umieścić w jednej zmiennej

  • skomplikowane dane - nieustannie potrzebujesz metod operowania na danych

Kiedy nie używać wyliczenia:

  • możesz akceptować wszystkie wartości jednego typu, a twoje stałe zawierają tylko te najczęściej używane
  • możesz akceptować ciągłe dane

    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);
            }
    
            ...
        }
    }
    
  • dla nazw (jak w twoim przykładzie)
  • dla wszystkiego innego, co naprawdę nie potrzebuje wyliczenia

Enum zajmuje więcej miejsca

  • pojedyncze odniesienie do stałej wyliczeniowej zajmuje 4 bajty
  • każda stała wyliczeniowa zajmuje miejsce, które jest sumą rozmiarów jej pól wyrównanych do 8 bajtów + narzut obiektu
  • sama klasa enum zajmuje trochę miejsca

Stałe zajmują mniej miejsca

  • stała nie ma odniesienia, więc są to czyste dane (nawet jeśli jest to odniesienie, to instancja wyliczenia byłaby odniesieniem do innego odniesienia)
  • stałe mogą być dodawane do istniejącej klasy - nie jest konieczne dodawanie kolejnej klasy
  • stałe mogą być wstawiane; zapewnia rozszerzone funkcje kompilacji (takie jak sprawdzanie wartości null, znajdowanie martwego kodu itp.)
Kamil Jarosz
źródło
2
Możesz użyć adnotacji @IntDef, aby zasymulować sprawdzanie typu dla stałych int. To jeden argument mniej na korzyść Java Enums.
BladeCoder,
3
@BladeCoder no, nie potrzebujesz odniesienia do stałej
Kamil Jarosz
1
Warto również zauważyć, że używanie wyliczeń sprzyja używaniu refleksji. Wiadomo, że użycie odbicia jest OGROMNYM hitem wydajnościowym dla Androida. Zobacz tutaj: blog.nimbledroid.com/2016/02/23/slow-Android-reflection.html
w3bshark
3
@ w3bshark W jaki sposób wyliczenia promują użycie refleksji?
Kevin Krumwiede
3
Inną rzeczą, o której należy pamiętać, jest zrzut sterty ze standardowego pustego komunikatu „Hello, world!” Projekt obejmuje ponad 4 000 klas i 700 000 obiektów. Nawet gdybyś miał absurdalnie wielką wyliczenie z tysiącami stałych, możesz być pewien, że efekt wydajności byłby nieistotny obok rozdęcia samego frameworka Androida.
Kevin Krumwiede
58

Jeśli wyliczenia mają po prostu wartości, powinieneś spróbować użyć IntDef / StringDef, jak pokazano tutaj:

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

Przykład: zamiast:

enum NavigationMode {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS} 

używasz:

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

aw funkcji, która ma to jako parametr / zwróconą wartość, użyj:

@NavigationMode
public abstract int getNavigationMode();

public abstract void setNavigationMode(@NavigationMode int mode);

W przypadku, gdy wyliczenie jest złożone, użyj wyliczenia. Nie jest tak źle.

Aby porównać wyliczenia ze stałymi wartościami, należy przeczytać tutaj:

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

Ich przykładem jest wyliczenie z 2 wartościami. Zajmuje 1112 bajtów w pliku dex w porównaniu do 128 bajtów, gdy używane są stałe liczby całkowite. Ma to sens, ponieważ wyliczenia są prawdziwymi klasami, w przeciwieństwie do tego, jak to działa w C / C ++.

programista Androida
źródło
Chociaż doceniam inne odpowiedzi zawierające zalety / wady wyliczeń, ta odpowiedź powinna być akceptowaną odpowiedzią, ponieważ zapewnia najlepsze rozwiązanie w szczególności dla Androida. Adnotacje wsparcia to droga do zrobienia. My, jako programiści Androida, powinniśmy pomyśleć „o ile nie mam mocnego powodu, aby używać wyliczeń, będę używać stałych z adnotacjami” zamiast sposobu myślenia innych odpowiedzi „chyba że później zacznę się martwić o pamięć / wydajność Potrafię używać wyliczeń ”. Powstrzymaj się teraz od późniejszych problemów z wydajnością!
w3bshark
3
@ w3bshark Jeśli masz problemy z wydajnością, wyliczenia prawdopodobnie nie będą pierwszą rzeczą, o której powinieneś pomyśleć, aby je rozwiązać. Adnotacje mają swoje własne kompromisy: nie mają obsługi kompilatora, nie mogą mieć własnych pól i metod, są żmudne w pisaniu, łatwo można zapomnieć o dodaniu adnotacji do int i tak dalej. Nie są one ogólnie lepszym rozwiązaniem, są po prostu tam, aby zaoszczędzić miejsce na stosie, gdy zajdzie taka potrzeba.
Malcolm,
1
@androiddeveloper Nie możesz popełnić błędu, przekazując stałą niezdefiniowaną w deklaracji wyliczenia. Ten kod nigdy się nie skompiluje. Jeśli podasz złą stałą z IntDefadnotacją, tak się stanie. Co do zegarków, myślę, że też jest całkiem jasne. Które urządzenie ma więcej mocy procesora, pamięci RAM i baterii: telefon czy zegarek? A które z nich wymagają oprogramowania, aby było bardziej zoptymalizowane pod kątem wydajności z tego powodu?
Malcolm,
3
@androiddeveloper OK, jeśli nalegasz na ścisłą terminologię, przez błędy miałem na myśli błędy, które nie są błędami kompilacji (błędy czasu wykonania lub logiki programu). Trollujesz mnie, czy naprawdę nie dostrzegasz różnicy między nimi a zaletami błędów w czasie kompilacji? To dobrze omówiony temat, poszukaj go w internecie. Jeśli chodzi o zegarki, Android zawiera więcej urządzeń, ale te najbardziej ograniczone będą najniższym wspólnym mianownikiem.
Malcolm,
2
@androiddeveloper Odpowiedziałem już na oba te stwierdzenia, powtórzenie ich jeszcze raz nie unieważnia tego, co powiedziałem.
Malcolm
12

Oprócz wcześniejszych odpowiedzi dodam, że jeśli korzystasz z Proguard (i zdecydowanie powinieneś to zrobić, aby zmniejszyć rozmiar i zaciemnić swój kod), to Twój Enumszostanie automatycznie przekonwertowany na@IntDef tam, gdzie jest to możliwe:

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

class / unboxing / enum

Upraszcza typy wyliczeniowe do stałych całkowitych, jeśli to możliwe.

Dlatego jeśli masz jakieś dyskretne wartości i jakaś metoda powinna pozwolić na pobranie tylko tych wartości, a nie innych tego samego typu, to użyłbym Enum , ponieważ Proguard wykona tę ręczną pracę optymalizacji kodu za mnie.

I oto dobry post o używaniu wyliczeń Jake'a Whartona, spójrz na to.

Jako programista biblioteki zdaję sobie sprawę z tych niewielkich optymalizacji, które należy wykonać, ponieważ chcemy mieć jak najmniejszy wpływ na rozmiar, pamięć i wydajność używanej aplikacji. Ale ważne jest, aby zdać sobie sprawę, że [...] umieszczenie wyliczenia w publicznym interfejsie API względem wartości całkowitych tam, gdzie jest to stosowne, jest całkowicie w porządku. Ważna jest świadomość różnicy w podejmowaniu świadomych decyzji

Gaket
źródło
11

Czy powinienem bezwzględnie unikać używania wyliczeń w systemie Android?

Nie. „ Ściśle ” oznacza, że ​​są tak złe, że w ogóle nie powinny być używane. Możliwe, że w ekstremalnej sytuacji mogą wystąpić problemy z wydajnością, takie jak wiele wielu (tysięcy lub milionów) operacji z wyliczeniami (kolejno w wątku interfejsu użytkownika). O wiele bardziej powszechne są sieciowe operacje we / wy, które powinny być wykonywane wyłącznie w wątku w tle. Najczęstszym wykorzystanie teksty stałe jest prawdopodobnie jakiś rodzaj czeku typu - czy przedmiot jest to albo że który jest tak szybka, że nie będzie w stanie dostrzec różnicę między jednym porównania teksty stałe i porównywania liczb.

Czy ktoś może wyjaśnić, który z nich jest najlepszy do reprezentowania tego samego w systemie Android?

Nie ma na to ogólnej zasady. Użyj tego, co działa dla Ciebie i pomoże Ci przygotować aplikację. Optymalizuj później - gdy zauważysz, że występuje wąskie gardło, które spowalnia niektóre aspekty aplikacji.

stan0
źródło
1
Dlaczego miałbyś optymalizować później, skoro równie łatwo jest używać stałych z adnotacjami pomocniczymi i optymalizować teraz? Zobacz odpowiedź @ android_developer tutaj.
w3bshark
4

Dwa fakty.

1, Enum jest jedną z najpotężniejszych funkcji JAVA.

2, telefon z Androidem ma zwykle dużo pamięci.

Więc moja odpowiedź brzmi NIE. Będę używać Enum w systemie Android.

Kai Wang
źródło
2
Jeśli Android ma DUŻO pamięci, nie oznacza to, że Twoja aplikacja powinna ją zużywać i nie zostawiać żadnej dla innych. Należy postępować zgodnie ze sprawdzonymi metodami, aby uchronić aplikację przed zabiciem przez system operacyjny Android. Nie mówię, nie używaj wyliczeń, ale używaj tylko wtedy, gdy nie masz alternatywy.
Varundroid
1
Zgadzam się z Tobą, że powinniśmy postępować zgodnie z najlepszymi praktykami, jednak najlepsza praktyka nie zawsze oznacza użycie najmniejszej ilości pamięci. Utrzymanie czystego i łatwego do zrozumienia kodu jest o wiele ważniejsze niż oszczędzanie kilku k pamięci. Musisz znaleźć równowagę.
Kai Wang
1
Zgadzam się z tobą, że musimy znaleźć równowagę, ale użycie TypeDef nie sprawi, że nasz kod będzie wyglądał źle lub trudniej w utrzymaniu. Używam go od ponad roku i nigdy nie czułem, że mój kod nie jest łatwy do zrozumienia, a także żaden z moich rówieśników nigdy nie narzekał, że TypeDefs są trudne do zrozumienia w porównaniu z wyliczeniami. Radzę używać wyliczeń tylko wtedy, gdy nie masz innej opcji, ale unikaj tego, jeśli możesz.
Varundroid
2

Chciałbym dodać, że nie możesz używać @Annotations, gdy deklarujesz List <> lub Map <>, gdzie klucz lub wartość jest jednym z twoich interfejsów adnotacji. Pojawia się błąd „Adnotacje są tu niedozwolone”.

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

Więc kiedy musisz spakować go do listy / mapy, użyj enum, ponieważ można je dodać, ale @annotated int / string group nie może.

Grisgram
źródło