Чи слід суворо уникати використання перелічень на Android?


93

Раніше я визначав набір пов’язаних констант, таких як Bundleключі, в інтерфейсі, як показано нижче:

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

Це надає мені кращий спосіб групувати пов'язані константи разом і використовувати їх, роблячи статичний імпорт (не реалізує). Я знаю , що Androidсистема також використовує константи в точно так же , як Toast.LENTH_LONG, View.GONE.

Однак я часто відчуваю, що це Java Enumsнабагато кращий та потужніший спосіб представити константу.

Але чи є проблема з ефективністю використання enumsна Android?

Трохи досліджень я закінчив у розгубленості. З цього питання "Уникайте перерахувань, де вам потрібні лише входи", вилученого із підказок щодо продуктивності Android? Зрозуміло, що Googleвилучено "Уникати перерахувань" зі своїх підказок щодо продуктивності, але з офіційних навчальних документів Будьте в курсі розділу накладних витрат на пам'ять, в якому чітко сказано: "Перелічення часто вимагають удвічі більше пам'яті, ніж статичні константи. Вам слід суворо уникати використання перерахувань на Android. " Це все ще добре? (Скажімо, у Javaверсіях після 1.6)

Ще одне питання, яке я помітив, - це пересилання enumsз intentsвикористанням, і Bundleя повинен надсилати їх шляхом серіалізації (тобто putSerializable(), я вважаю, що дорога операція порівняно з примітивним putString()методом, хоч і enumsнадає її безкоштовно).

Хтось може пояснити, який із них найкраще представляти те саме Android? Чи слід суворо уникати використання enumsна Android?


5
Вам слід скористатися наявними у вас інструментами. Насправді діяльність або фрагмент займає багато пам'яті та використання процесора, але це не причина припиняти їх використання. Використовуйте статичний int, якщо вам це потрібно, і використовуйте перелічення, коли вони вам потрібні.
Патрік

11
Я згоден. Це пахне передчасною оптимізацією. Якщо у вас немає проблем із продуктивністю та / або пам’яттю, і ви не зможете довести шляхом профілювання, що перелічення є причиною, використовуйте їх там, де це має сенс.
GreyBeardedGeek

2
Раніше вважалося, що енуми понесли покарання за нетривальну ефективність, але новіші тести не показують користі від використання констант. Див. Stackoverflow.com/questions/24491160/… , а також stackoverflow.com/questions/5143256/…
Бенджамін Серджент,

1
Щоб уникнути покарання за продуктивність за серіалізацію Enum у наборі, Enum.ordinal()замість цього ви можете передати його як int .
BladeCoder

1
нарешті, є кілька пояснень щодо проблем з продуктивністю на Enum тут youtube.com/watch?v=Hzs6OBcvNQE
nvinayshetty

Відповіді:


116

Використовуйте, enumколи вам потрібні його функції. Не уникайте цього строго .

Java enum є потужнішим, але якщо вам не потрібні його функції, використовуйте константи, вони займають менше місця, і вони самі по собі можуть бути примітивними.

Коли використовувати enum:

  • перевірка типу - ви можете приймати лише перелічені значення, і вони не є безперервними (див. нижче те, що я тут називаю безперервним )
  • перевантаження методу - кожна константа переліку має свою реалізацію методу

    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);
    }
  • більше даних - ваша одна константа містить більше однієї інформації, яку не можна помістити в одну змінну

  • складні дані - ваші постійні методи роботи з даними

Коли ні використовувати перелік:

  • Ви можете приймати всі значення одного типу, а ваші константи містять лише ті, що найчастіше використовуються
  • Ви можете приймати безперервні дані

    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);
            }
    
            ...
        }
    }
  • для імен (як у вашому прикладі)
  • для всього іншого, що насправді не потребує переліку

Перерахування займають більше місця

  • одне посилання на константу переліку займає 4 байти
  • кожна константа переліку займає простір, який є сумою розмірів її полів, вирівняних до 8 байт + накладні витрати на об'єкт
  • клас enum займає певний простір

Константи займають менше місця

  • константа не має посилання, тому це чисті дані (навіть якщо це посилання, тоді екземпляр переліку буде посиланням на інше посилання)
  • константи можуть бути додані до існуючого класу - необов’язково додавати інший клас
  • константи можуть бути вбудовані; він надає розширені функції часу компіляції (такі як перевірка нуля, пошук мертвого коду тощо)

2
Ви можете використовувати анотацію @IntDef для імітації перевірки типу на наявність int-констант. Це ще один аргумент менше на користь Java Enums.
BladeCoder

3
@BladeCoder ні, вам не потрібно посилання на константу
Каміль Ярош

1
Також добре зауважити, що використання перелічень сприяє використанню рефлексії. І відомо, що використання відображення - ВЕЛИЧЕЗНИЙ показник продуктивності для Android. Дивіться тут: blog.nimbledroid.com/2016/02/23/slow-Android-reflection.html
w3bshark

3
@ w3bshark Як перелічення сприяють використанню рефлексії?
Кевін Крумвіде,

3
Інша річ, про яку слід пам’ятати, - це те, що дамп купи зі стандартного порожнього "Привіт, світе!" Проект включає понад 4000 класів та 700 000 об'єктів. Навіть якщо у вас був смішно величезний перелік з тисячами констант, ви можете бути впевнені, що ефект продуктивності буде незначним поряд із роздуттям самого фреймворку Android.
Кевін Крумвіде,

57

Якщо перелічення просто мають значення, спробуйте використати IntDef / StringDef, як показано тут:

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

Приклад: замість:

enum NavigationMode {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS} 

ти використовуєш:

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

а у функції, яка має його як параметр / повернене значення, використовуйте:

@NavigationMode
public abstract int getNavigationMode();

public abstract void setNavigationMode(@NavigationMode int mode);

Якщо перелік складний, використовуйте перелік. Це не так погано.

Щоб порівняти перелічення та постійні значення, вам слід прочитати тут:

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

Їх приклад - перелік з 2 значеннями. Це займає 1112 байт у файлі dex порівняно із 128 байтами, коли використовуються постійні цілі числа. Має сенс, оскільки перелічення - це реальні класи, на відміну від того, як це працює на C / C ++.


Хоча я ціную інші відповіді, які надають плюси / мінуси перелічень, ця відповідь повинна бути прийнятою відповіддю, оскільки вона забезпечує найкраще рішення для Android, зокрема. Анотації підтримки - це шлях. Ми, як розробники Android, повинні думати "якщо у мене немає вагомих причин використовувати перелічення, я буду використовувати константи з анотаціями", а не спосіб мислення інших відповідей ", якщо пізніше я не почну турбуватися про пам'ять / продуктивність, Я можу використовувати перелічення ". Зупиніться зараз, щоб потім не мати проблем із продуктивністю!
w3bshark

3
@ w3bshark Якщо у вас виникають проблеми з продуктивністю, перерахування навряд чи будуть першим, про що вам слід подумати, щоб їх вирішити. Анотації мають власні компроміси: вони не мають підтримки компілятора, вони не можуть мати своїх полів і методів, їм нудно писати, ви можете легко забути коментувати int тощо. В цілому вони не є кращим рішенням, вони просто там, щоб заощадити кучу простору, коли вам потрібно.
Малкольм

1
@androiddeveloper Ви не можете помилитися, передавши константу, не визначену в декларації переліку. Цей код ніколи не компілюється. Якщо ви передасте неправильну константу з IntDefанотацією, це буде. Щодо годинників, я думаю, це теж досить ясно. Який пристрій має більше потужності процесора, оперативної пам’яті та акумулятора: телефон чи годинник? І яким із них потрібне програмне забезпечення, щоб оптимізувати його для підвищення продуктивності?
Малкольм

3
@androiddeveloper Добре, якщо ви наполягаєте на суворій термінології, під помилками я мав на увазі помилки, які не є помилками часу компіляції (помилки часу виконання або програмної логіки). Ти мене тролюєш чи справді не отримуєш різниці між ними та перевагами помилок під час компіляції? Це добре обговорювана тема, шукайте це в Інтернеті. Щодо годинників, Android дійсно включає більше пристроїв, але найбільш обмеженими будуть найнижчий загальний знаменник.
Малкольм

2
@androiddeveloper Я вже відповів на обидва ці твердження, повторивши їх ще раз, не робить недійсним те, що я сказав.
Малкольм

12

На додаток до попередніх відповідей, я хотів би додати, що якщо ви використовуєте Proguard (і ви обов’язково повинні це зробити, щоб зменшити розмір і затуманити свій код), ваш Enumsавтоматично буде перетворений туди, @IntDefде це можливо:

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

клас / розпакування / перелік

Спрощує типи переліку до цілочисельних констант, коли це можливо.

Отже, якщо у вас є деякі дискретні значення, і якийсь метод повинен дозволяти приймати лише ці значення, а не інші того самого типу, тоді я б скористався Enum, тому що Proguard зробить цю ручну роботу з оптимізації коду для мене.

І ось хороший допис про використання переліків від Джейка Уортона, погляньте на це.

Як розробник бібліотеки, я усвідомлюю ці невеликі оптимізації, які слід робити, оскільки ми хочемо мати якомога менший вплив на розмір, пам’ять та продуктивність додатка. Але важливо усвідомити, що [...] розміщення переліку у вашому загальнодоступному API порівняно із цілими значеннями, де це доречно, цілком нормально. Знання різниці для прийняття обґрунтованих рішень - ось що важливо


11

Чи слід суворо уникати використання перелічень на Android?

Ні. " Строго " означає, що вони такі погані, що їх взагалі не слід використовувати. Можливо, проблеми з продуктивністю можуть виникнути в екстремальній ситуації, як багато багатьох багатьох (тисяч або мільйонів) операцій з переліченнями (послідовними в потоці інтерфейсу користувача). Набагато більш поширеними є операції мережевого вводу-виводу, які повинні строго відбуватися у фоновому потоці. Найпоширенішим використанням перерахувань є, мабуть, якась перевірка типу - чи є об’єкт тим чи тим, який настільки швидкий, що ви не зможете помітити різницю між одним порівнянням перелічень та порівнянням цілих чисел.

Хтось може пояснити, який найкращий спосіб представити те саме в Android?

Для цього не існує загального емпіричного правила. Використовуйте те, що вам підходить, і допомагає підготувати програму. Оптимізуйте пізніше - після того, як ви помітите, що вузьке місце уповільнює деякі аспекти вашого додатка.


1
Чому б вам оптимізувати пізніше, коли так само просто використовувати константи з анотаціями підтримки та оптимізувати зараз? Дивіться відповідь @ android_developer тут.
w3bshark

11

З Android P Google не має обмежень / заперечень щодо використання перерахувань

Документація змінилася там, де раніше рекомендували бути обережними, але зараз про це не згадується. https://developer.android.com/reference/java/lang/Enum


1
Чи є якісь інші докази, крім цього: я знайшов заяву @JakeWharton про це: twitter.com/jakewharton/status/1067790191237181441 . Будь-хто може перевірити байт-код.
soshial

4

Два факти.

1, Enum - одна з найпотужніших функцій у JAVA.

2, телефон Android зазвичай має БАГАТО пам’яті.

Тож моя відповідь - НІ. Я буду використовувати Enum в Android.


2
Якщо Android має БАГАТО пам’яті, це не означає, що ваш додаток повинен споживати все це і не залишати нічого для інших. Вам слід дотримуватися найкращих практик, щоб врятувати свою програму від загибелі ОС Android. Я не кажу, що не використовуйте перелічення, але використовуйте лише тоді, коли у вас немає альтернативи.
Варундроїд

1
Я погоджуюсь з вами, що нам слід дотримуватися найкращих практик, однак найкраща практика не завжди означає використання найменшої пам'яті. Підтримка коду чистим і легким для розуміння набагато важливіше, ніж економія декількох к пам’яті. Вам потрібно знайти баланс.
Kai Wang

1
Я погоджуюсь з вами, що нам потрібно знайти баланс, але використання TypeDef не призведе до того, що наш код буде виглядати погано або менш ремонтопридатним. Я використовую його вже більше року і ніколи не відчував, що мій код непростий для розуміння, також ніхто з моїх однолітків ніколи не скаржився, що TypeDefs важко зрозуміти порівняно з переліками. Я раджу використовувати enum лише тоді, коли у вас немає іншого варіанту, але уникайте його, якщо можете.
Варундроїд

2

Я хотів би додати, що ви не можете використовувати @Anotations, коли оголошуєте List <> або Map <>, де ключ або значення є одним із ваших інтерфейсів анотацій. Ви отримуєте помилку "Анотації тут заборонені".

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

Отже, коли вам потрібно упакувати його у список / карту, використовуйте enum, оскільки їх можна додати, але групи @annotated int / string не можуть.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.