Enum.values ​​() vs EnumSet.allOf(). Какой из них предпочтительнее?

Я смотрел под капотом на EnumSet.allOf, и он выглядит очень эффективно, особенно для перечислений с менее чем 64 значениями.

В основном все наборы совместно используют один массив из всех возможных значений enum, и единственная другая часть информации - это битмаска, которая в случае allOf устанавливается одним махом.

С другой стороны Enum.values ​​() кажется немного черной магией. Кроме того, он возвращает массив, а не коллекцию, поэтому во многих случаях он должен быть украшен Arrays.asList() для использования в любом месте, которое ожидает коллекцию.

Итак, если EnumSet.allOf предпочтительнее Enum.values?

Более конкретно, какую форму итератора for следует использовать:

for ( final MyEnum val: MyEnum.values( ) );

или

for ( final MyEnum val: EnumSet.allOf( MyEnum.class ) );

Ответы

Ответ 1

Поскольку я не получил ответа на мой вопрос, по которому более эффективен, я решил провести собственное тестирование.

Я тестировал итерацию по values(), Arrays.asList( values() ) и EnumSet.allOf( ). Я повторил эти тесты 10 000 000 раз для разных размеров перечислений. Вот результаты теста:

oneValueEnum_testValues         1.328
oneValueEnum_testList           1.687
oneValueEnum_testEnumSet        0.578

TwoValuesEnum_testValues        1.360
TwoValuesEnum_testList          1.906
TwoValuesEnum_testEnumSet       0.797

ThreeValuesEnum_testValues      1.343
ThreeValuesEnum_testList        2.141
ThreeValuesEnum_testEnumSet     1.000

FourValuesEnum_testValues       1.375
FourValuesEnum_testList         2.359
FourValuesEnum_testEnumSet      1.219

TenValuesEnum_testValues        1.453
TenValuesEnum_testList          3.531
TenValuesEnum_testEnumSet       2.485

TwentyValuesEnum_testValues     1.656
TwentyValuesEnum_testList       5.578
TwentyValuesEnum_testEnumSet    4.750

FortyValuesEnum_testValues      2.016
FortyValuesEnum_testList        9.703
FortyValuesEnum_testEnumSet     9.266

Это результаты для тестов, запущенных из командной строки. Когда я запускал эти тесты из Eclipse, я получил подавляющую поддержку testValues. В основном он был меньше EnumSet даже для небольших перечислений. Я считаю, что увеличение производительности происходит от оптимизации итератора массива в цикле for ( val : array ).

С другой стороны, как только вам понадобится java.util.Collection, чтобы пройти, Arrays.asList( ) теряет до EnumSet.allOf, особенно для небольших перечислений, которые, я считаю, будут большинством в любой заданной кодовой базе.

Итак, я бы сказал, что вы должны использовать

for ( final MyEnum val: MyEnum.values( ) )

но

Iterables.filter(
    EnumSet.allOf( MyEnum.class ),
    new Predicate< MyEnum >( ) {...}
)

И используйте только Arrays.asList( MyEnum.values( ) ), где java.util.List абсолютно необходимо.

Ответ 2

Вы должны использовать самый простой и понятный для вас подход. Производительность не должна рассматриваться в большинстве ситуаций.

IMHO: ни одна из них не работает очень хорошо, поскольку они оба создают объекты. Один в первом случае и три во втором. Вы можете построить константу, которая содержит все значения по соображениям производительности.

Ответ 3

Существует также Class.getEnumConstants()

под капотом все они все равно называют values() методами типов перечислений, через отражение.

Ответ 4

Метод values() более ясен и эффективен, если вы просто хотите перебрать все возможные значения enum. Значения кэшируются классом (см. Class.getEnumConstants())

Если вам нужно подмножество значений, вы должны использовать EnumSet. Начните с allOf() или noneOf() и добавьте или удалите значения или используйте только of() по мере необходимости.

Ответ 5

Не то, чтобы я прошел всю реализацию, но мне кажется, что EnumSet.allOf() в основном использует ту же инфраструктуру, что и .values ​​(). Поэтому я ожидаю, что EnumSet.allOf() потребует некоторых (возможно, незначительных) дополнительных шагов (см. http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6276988).

Мне кажется ясным, что предполагаемое использование foreach for(MyEnum val : MyEnum.values()) почему это происходит по-другому? Вы только смутите программиста по обслуживанию.

Я имею в виду, если вам нужна коллекция, вы должны ее получить. Если вы хотите использовать foreach, массивы достаточно хороши. Я даже предпочитаю массивы при нажатии! Зачем что-то обертывать чем угодно, если то, что вы получили (массив), достаточно хорошо? Простые вещи обычно быстрее.

В любом случае, Питер Лори прав. Не беспокойтесь о производительности этого. Это достаточно быстро, и есть вероятность, что есть миллионы других узких мест, которые делают эту крошечную теоретическую разницу в производительности совершенно неактуальной (не смей его точку создания объекта). например, на 100% нормально).

Ответ 6

EnumSet не построен с намерением перебрать его значения. Скорее он реализован с идеей, чтобы он представлял BitMap или BitMask эффективно (или достаточно эффективно). В javadoc на EnumSet также указано:

Множества Enum представлены внутри как битовые векторы. Это представление чрезвычайно компактно и эффективно. Пространство и время работы этого класса должны быть достаточно хорошими, чтобы позволить использовать его как высококачественную, типичную альтернативу традиционным "бит-флагам" на основе int. Даже массовые операции (такие как containsAll и keepAll) должны выполняться очень быстро, если их аргумент также является перечислением.

Поскольку только один бит может представлять определенное значение Enum, он также реализуется как Set, а не как List.

Теперь, вероятно, также верно, что вы можете выполнить то же самое и быстрее, используя битовые маски C-стиля (x ^ 2), однако он предлагает более интуитивно понятный стиль кодирования и безопасное использование типов с использованием перечислений, и он расширяется легко превышающий размер того, что может содержать int или long.

Таким образом, вы можете проверить, что все биты установлены следующим образом:

public class App {
  enum T {A,B}
  public static void main(String [] args) {
    EnumSet<T> t = EnumSet.of(T.A);
    t.containsAll(EnumSet.allOf(T.class));
  }
}