Java: Enum vs. Int

При использовании флагов в Java я видел два основных подхода. Один использует значения int и строку операторов if-else. Другой - использовать перечисления и инструкции case-switch.

Мне было интересно, есть ли разница в использовании памяти и скорости между использованием enums vs ints для флагов?

Ответ 1

Оба ints и enums могут использовать как switch, так и if-then-else, а использование памяти также минимально для обоих, а скорость похожа - нет существенной разницы между ними в точках, которые вы подняли.

Однако наиболее важным отличием является проверка типа. enums, ints не указаны.

Рассмотрим этот код:

public class SomeClass {
    public static int RED = 1;
    public static int BLUE = 2;
    public static int YELLOW = 3;
    public static int GREEN = 3; // sic

    private int color;

    public void setColor(int color) {
        this.color = color;
    }   
}

Хотя многие клиенты будут использовать это правильно,

new SomeClass().setColor(SomeClass.RED);

Ничего не мешает им написать это:

new SomeClass().setColor(999);

Существуют три основные проблемы с использованием шаблона public static final:

  • Проблема возникает во время выполнения, а не во время компиляции, поэтому исправление будет более дорогостоящим и сложнее найти причину
  • Вам нужно написать код для обработки плохого ввода - обычно if-then-else с окончательным else throw new IllegalArgumentException("Unknown color " + color); - снова дорогим
  • Ничего не мешает столкновению констант - код выше кода будет компилироваться, хотя YELLOW и GREEN имеют одинаковое значение 3

Если вы используете enums, вы решаете все эти проблемы:

  • Ваш код не будет компилироваться, если вы не передадите действительные значения в
  • Не нужно никакого специального кода "плохого ввода" - компилятор обрабатывает это для вас.
  • Значения перечисления уникальны

Ответ 2

Вы даже можете использовать Enums для замены этих побитовых комбинированных флагов, например int flags = FLAG_1 | FLAG_2;

Вместо этого вы можете использовать typeafe EnumSet:

Set<FlagEnum> flags = EnumSet.of(FlagEnum.FLAG_1, FlagEnum.FLAG_2);

// then simply test with contains()
if(flags.contains(FlagEnum.FLAG_1)) ...

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

Ответ 3

Использование памяти и скорость не являются важными соображениями. Вы не сможете измерить разницу в любом случае.

Я думаю, что перечисления должны быть предпочтительными, когда они применяются, потому что подчеркивают тот факт, что выбранные значения идут вместе и содержат замкнутое множество. Считываемость также улучшена. Код с использованием перечислений является более самодокументированным, чем паразитные значения int, разбросанные по всему вашему коду.

Предпочитают перечисления.

Ответ 4

Одна из причин, по которой вы увидите код с использованием флагов int вместо enum, - это то, что Java не имеет перечислений до тех пор, пока Java 1.5

Итак, если вы смотрите на код, который был первоначально написан для более старой версии Java, тогда был доступен только шаблон int.

Существует очень небольшое количество мест, где использование флага int по-прежнему предпочтительнее в современном Java-коде, но в большинстве случаев вам следует использовать enum из-за безопасности и выразительности типов, которые они предлагают.

С точки зрения эффективности, это будет зависеть от того, как они используются. JVM обрабатывает оба типа очень эффективно, но метод int, вероятно, будет несколько более эффективным для некоторых случаев использования (поскольку они обрабатываются как примитивные, а не объекты), но в других случаях перечисление будет более эффективным (поскольку оно не " t нужно пойти бросить бокс /unboxing ).

Вам будет сложно найти ситуацию, при которой разница в эффективности будет каким-либо образом заметна в реальном мире, поэтому вы должны принять решение на основе качества кода ( читаемость и безопасность), что должно привести к использованию перечисления в 99% случаев.

Ответ 5

Имейте в виду, что enums безопасны по типу, и вы не можете смешивать значения из одного перечисления с другим. Это хорошая причина предпочесть enums над ints для флагов.

С другой стороны, если вы используете ints для своих констант, вы можете смешать значения из несвязанных констант, например:

public static final int SUNDAY = 1;
public static final int JANUARY = 1;

...

// even though this works, it a mistake:
int firstMonth = SUNDAY;

Использование памяти enums over ints незначительно, а безопасность типа enums обеспечивает минимальные накладные расходы.

Ответ 6

Отвечайте на свой вопрос: Нет, после незначительного времени загрузки класса Enum производительность будет одинаковой.

Как утверждали другие, оба типа могут использоваться в операторах switch или if else. Кроме того, как заявили другие, вы должны одобрить Enums над флагами int, потому что они были предназначены для замены этого шаблона и обеспечивают дополнительную безопасность.

ОДНАКО, есть более хороший образец, который вы считаете. Предоставляя любое значение, которое должно было выражать оператор switch/if, который должен был выражаться как свойство.

Посмотрите на эту ссылку: http://docs.oracle.com/javase/1.5.0/docs/guide/language/enums.html Обратите внимание на шаблон, предоставленный для получения масс и радиусов планет. Предоставление собственности таким образом гарантирует, что вы не забудете закрыть случай, если вы добавите перечисление.

Ответ 7

Мне нравится использовать Enums, когда это возможно, но у меня была ситуация, когда мне приходилось вычислять миллионы смещений файлов для разных типов файлов, которые я определил в перечислении, и мне пришлось выполнить оператор switch десятки миллионов раз для вычисления основание смещения по типу перечисления. Я проверил следующий тест:

import java.util.Random;

открытый класс switchTest {   public enum MyEnum   {       Value1, Value2, Value3, Value4, Value5   };

public static void main(String[] args)
{
    final String s1 = "Value1";
    final String s2 = "Value2";
    final String s3 = "Value3";
    final String s4 = "Value4";
    final String s5 = "Value5";

    String[] strings = new String[]
    {
        s1, s2, s3, s4, s5
    };

    Random r = new Random();

    long l = 0;

    long t1 = System.currentTimeMillis();

    for(int i = 0; i < 10_000_000; i++)
    {
        String s = strings[r.nextInt(5)];

        switch(s)
        {
            case s1:
                // make sure the compiler can't optimize the switch out of existence by making the work of each case it does different
                l = r.nextInt(5);
                break;
            case s2:
                l = r.nextInt(10);
                break;
            case s3:
                l = r.nextInt(15);
                break;
            case s4:
                l = r.nextInt(20);
                break;
            case s5:
                l = r.nextInt(25);
                break;
        }
    }

    long t2 = System.currentTimeMillis();

    for(int i = 0; i < 10_000_000; i++)
    {
        MyEnum e = MyEnum.values()[r.nextInt(5)];

        switch(e)
        {
            case Value1:
                // make sure the compiler can't optimize the switch out of existence by making the work of each case it does different
                l = r.nextInt(5);
                break;
            case Value2:
                l = r.nextInt(10);
                break;
            case Value3:
                l = r.nextInt(15);
                break;
            case Value4:
                l = r.nextInt(20);
                break;
            case Value5:
                l = r.nextInt(25);
                break;
        }
    }

    long t3 = System.currentTimeMillis();

    for(int i = 0; i < 10_000_000; i++)
    {
        int xx = r.nextInt(5);

        switch(xx)
        {
            case 1:
                // make sure the compiler can't optimize the switch out of existence by making the work of each case it does different
                l = r.nextInt(5);
                break;
            case 2:
                l = r.nextInt(10);
                break;
            case 3:
                l = r.nextInt(15);
                break;
            case 4:
                l = r.nextInt(20);
                break;
            case 5:
                l = r.nextInt(25);
                break;
        }
    }

    long t4 = System.currentTimeMillis();

    System.out.println("strings:" + (t2 - t1));
    System.out.println("enums  :" + (t3 - t2));
    System.out.println("ints   :" + (t4 - t3));
}

}

и получили следующие результаты:

строки: 442

перечисления: 455

ints: 362

Итак, из этого я решил, что для меня перечисления были достаточно эффективными. Когда я уменьшал количество циклов до 1М от 10М, строка и перечисления занимали примерно в два раза больше, чем int, что указывает на то, что было некоторое накладное время для использования строк и перечислений в первый раз по сравнению с ints.

Ответ 8

Да, есть разница. В современных 64-битных значениях Java значения Enum являются, по существу, указателями на объекты, и они либо принимают 64 бита (не сжатые операционные системы), либо используют дополнительный CPU (сжатые операционные системы).

Мой тест показал снижение производительности на 10% для перечислений (1.8u25, AMD FX-4100): 13k ns против 14k ns

Источник тестирования ниже:

public class Test {

    public static enum Enum {
        ONE, TWO, THREE
    }

    static class CEnum {
        public Enum e;
    }

    static class CInt {
        public int i;
    }

    public static void main(String[] args) {
        CEnum[] enums = new CEnum[8192];
        CInt[] ints = new CInt[8192];

        for (int i = 0 ; i < 8192 ; i++) {
            enums[i] = new CEnum();
            ints[i] = new CInt();
            ints[i].i = 1 + (i % 3);
            if (i % 3 == 0) {
                enums[i].e = Enum.ONE;
            } else if (i % 3 == 1) {
                enums[i].e = Enum.TWO;
            } else {
                enums[i].e = Enum.THREE;
            }
        }
        int k=0; //calculate something to prevent tests to be optimized out

        k+=test1(enums);
        k+=test1(enums);
        k+=test1(enums);
        k+=test1(enums);
        k+=test1(enums);
        k+=test1(enums);
        k+=test1(enums);
        k+=test1(enums);
        k+=test1(enums);
        k+=test1(enums);

        System.out.println();

        k+=test2(ints);
        k+=test2(ints);
        k+=test2(ints);
        k+=test2(ints);
        k+=test2(ints);
        k+=test2(ints);
        k+=test2(ints);
        k+=test2(ints);
        k+=test2(ints);
        k+=test2(ints);

        System.out.println(k);



    }

    private static int test2(CInt[] ints) {
        long t;
        int k = 0;
        for (int i = 0 ; i < 1000 ; i++) {
            k+=test(ints);
        }

        t = System.nanoTime();
        k+=test(ints);
        System.out.println((System.nanoTime() - t)/100 + "ns");
        return k;
    }

    private static int test1(CEnum[] enums) {
        int k = 0;
        for (int i = 0 ; i < 1000 ; i++) {
            k+=test(enums);
        }

        long t = System.nanoTime();
        k+=test(enums);
        System.out.println((System.nanoTime() - t)/100 + "ns");
        return k;
    }

    private static int test(CEnum[] enums) {
        int i1 = 0;
        int i2 = 0;
        int i3 = 0;

        for (int j = 100 ; j != 0 ; --j)
        for (int i = 0 ; i < 8192 ; i++) {
            CEnum c = enums[i];
            if (c.e == Enum.ONE) {
                i1++;
            } else if (c.e == Enum.TWO) {
                i2++;
            } else {
                i3++;
            }
        }

        return i1 + i2*2 + i3*3;
    }

    private static int test(CInt[] enums) {
        int i1 = 0;
        int i2 = 0;
        int i3 = 0;

        for (int j = 100 ; j != 0 ; --j)
        for (int i = 0 ; i < 8192 ; i++) {
            CInt c = enums[i];
            if (c.i == 1) {
                i1++;
            } else if (c.i == 2) {
                i2++;
            } else {
                i3++;
            }
        }

        return i1 + i2*2 + i3*3;
    }
}

Ответ 9

Даже если этот вопрос старый, я хотел бы указать, что вы не можете сделать с помощью ints

public interface AttributeProcessor {
    public void process(AttributeLexer attributeLexer, char c);
}

public enum ParseArrayEnd implements AttributeProcessor {
    State1{
        public void process(AttributeLexer attributeLexer, char c) {
            .....}},
    State2{
        public void process(AttributeLexer attributeLexer, char c) {
            .....}}
}

И что вы можете сделать, это сделать карту того, какое значение ожидается как ключ, а перечисление в качестве значения,

Map<String, AttributeProcessor> map 
map.getOrDefault(key, ParseArrayEnd.State1).process(this, c);