Должен ли я строго избегать использования перечислений на Android?

93

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

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

Это дает мне более удобный способ сгруппировать связанные константы вместе и использовать их путем статического импорта (не реализует). Я знаю , что Androidсистема также использует константы в точно так же , как Toast.LENTH_LONG, View.GONE.

Однако мне часто кажется, что они Java Enumsпредоставляют гораздо лучший и эффективный способ представления константы.

Но есть проблема Performence в использовании enumsна Android?

Проведя небольшое исследование, я запутался. От этого вопрос ? «Избегайте Перечисления Где Вам только нужен Ints» удален от кончиков производительности Android, стало ясно , что Googleсняли „Избегайте перечисления“ от его советов по повышению эффективности, но от его официальных учебных документов Осознайте накладные памяти раздела ясно говорит: «перечисления часто требует вдвое больше памяти, чем статические константы. Вам следует строго избегать использования перечислений на Android. " Это все еще в силе? (Скажем, в Javaверсиях после 1.6)"

Еще одна проблема, которую я заметил, - это отправка enumsс intentsиспользованием, Bundleя должен отправить их путем сериализации (то есть putSerializable(), что я считаю дорогостоящей операцией по сравнению с примитивным putString()методом, хотя и enumsпредоставляет ее бесплатно).

Может кто-нибудь пояснить, какой из них лучше всего представляет то же самое Android? Должен ли я строго избегать использования enumsна Android?

нвинашетты
источник
5
Вам следует использовать имеющиеся у вас инструменты. Фактически, действие или фрагмент занимает много памяти и ресурсов процессора, но это не причина прекращать их использование. Используйте static int, если вам это нужно, и перечисления, когда они вам нужны.
Патрик
11
Согласен. Это пахнет преждевременной оптимизацией. Если у вас нет проблем с производительностью и / или памятью и вы не можете доказать с помощью профилирования, что перечисления являются причиной, используйте их там, где это имеет смысл.
GreyBeardedGeek
2
Раньше считалось, что перечисления приводят к нетривиальному снижению производительности, но более поздние тесты не показывают преимущества использования вместо них констант. См. Stackoverflow.com/questions/24491160/…, а также stackoverflow.com/questions/5143256/…
Бенджамин Сержент
1
Чтобы избежать потери производительности при сериализации Enum в Bundle, вы можете передать его как int, используя Enum.ordinal()вместо этого.
BladeCoder
1
наконец, есть некоторые объяснения проблем с производительностью на Enum здесь youtube.com/watch?v=Hzs6OBcvNQE
nvinayshetty

Ответы:

116

Используйте, enumкогда вам нужны его функции.Не избегайте этого строго .

Перечисление Java более мощное, но если вам не нужны его возможности, используйте константы, они занимают меньше места и сами могут быть примитивными.

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

  • сложные данные - вам постоянно нужны методы для работы с данными

Когда не использовать enum:

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

    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 занимает некоторое пространство

Константы занимают меньше места

  • константа не имеет ссылки, поэтому это чистые данные (даже если это ссылка, тогда экземпляр enum будет ссылкой на другую ссылку)
  • константы могут быть добавлены к существующему классу - нет необходимости добавлять другой класс
  • константы могут быть встроены; он предоставляет расширенные функции времени компиляции (такие как проверка нуля, поиск мертвого кода и т. д.)
Камил Ярош
источник
2
Вы можете использовать аннотацию @IntDef для имитации проверки типа для констант типа int. Это на один аргумент меньше в пользу Java Enums.
BladeCoder
3
@BladeCoder нет, вам не нужна ссылка на константу
Камил Ярош
1
Также хорошо отметить, что использование перечислений способствует использованию отражения. И известно, что использование отражения - ОГРОМНЫЙ удар производительности для Android. Смотрите здесь: blog.nimbledroid.com/2016/02/23/slow-Android-reflection.html
w3bshark
3
@ w3bshark Как перечисления способствуют использованию отражения?
Kevin Krumwiede
3
Еще нужно иметь в виду, что дамп кучи из стандартного пустого "Hello, world!" Проект включает более 4 000 классов и 700 000 объектов. Даже если у вас есть смехотворно огромное перечисление с тысячами констант, вы можете быть уверены, что эффект производительности будет незначительным по сравнению с раздуванием самой платформы Android.
Кевин Крамвиде
58

Если перечисления просто имеют значения, вам следует попробовать использовать 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

Их пример - перечисление с двумя значениями. В файле dex требуется 1112 байт по сравнению со 128 байтами, когда используются постоянные целые числа. Имеет смысл, поскольку перечисления - это реальные классы, а не то, как они работают на C / C ++.

разработчик Android
источник
Хотя я ценю другие ответы, содержащие плюсы / минусы перечислений, этот ответ должен быть принятым ответом, поскольку он обеспечивает лучшее решение для Android, в частности. Аннотации поддержки - это то, что вам нужно. Мы, как разработчики Android, должны думать: «Если у меня нет веских причин для использования перечислений, я буду использовать константы с аннотациями», а не образ мышления других ответов «если я не начну беспокоиться о памяти / производительности позже, Я могу использовать перечисления ". Избегайте проблем с производительностью в будущем!
w3bshark 05
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 вместо целочисленных значений там, где это необходимо, совершенно нормально. Важно понимать разницу, чтобы принимать обоснованные решения

Gaket
источник
11

Должен ли я строго избегать использования перечислений на Android?

Нет. « Строго » означает, что они настолько плохи, что их вообще нельзя использовать. Возможно, проблемы с производительностью могут возникнуть в экстремальной ситуации, например, при выполнении многих, многих (тысяч или миллионов) операций с перечислениями (последовательных в потоке пользовательского интерфейса). Гораздо более распространены сетевые операции ввода-вывода, которые должны выполняться строго в фоновом потоке. Наиболее распространенное использование перечислений - это, вероятно, своего рода проверка типа - является ли объект тем или иным , который работает настолько быстро, что вы не сможете заметить разницу между одним сравнением перечислений и сравнением целых чисел.

Может кто-нибудь пояснить, какой из них лучше всего представляет то же самое в Android?

Для этого нет общего практического правила. Используйте все, что вам подходит и помогает подготовить ваше приложение. Оптимизируйте позже - после того, как вы заметите узкое место, которое замедляет работу какого-либо аспекта вашего приложения.

stan0
источник
1
Зачем вам проводить оптимизацию позже, когда использовать константы с поддержкой аннотаций так же просто, как и оптимизировать сейчас? См. Ответ @ android_developer здесь.
w3bshark 05
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. Я не говорю, что не используйте перечисления, а используйте только тогда, когда у вас нет альтернативы.
Varundroid
1
Я согласен с вами, что мы должны следовать лучшим практикам, однако лучшие практики не всегда означают использование наименьшего количества памяти. Сохранение кода чистым и легким для понимания гораздо важнее, чем экономия нескольких k памяти. Вам нужно найти баланс.
Кай Ван
1
Я согласен с вами, что нам нужно найти баланс, но использование TypeDef не сделает наш код плохим или менее удобным для сопровождения. Я использую его уже больше года и никогда не чувствовал, что мой код непрост для понимания, а также никто из моих коллег никогда не жаловался на то, что TypeDef труднее понять по сравнению с перечислениями. Я советую использовать перечисления только тогда, когда у вас нет другого выбора, но избегайте этого, если можете.
Varundroid
2

Мне нравится добавлять, что вы не можете использовать @Annotations, когда объявляете 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 не могут.

Grisgram
источник