Java: Enum vs. Int - PullRequest
       8

Java: Enum vs. Int

57 голосов
/ 13 февраля 2012

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

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

Ответы [ 9 ]

123 голосов
/ 13 февраля 2012

И 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, вы решаете все эти проблемы:

  • Ваш код не скомпилируется, если вы не передадите действительные значения в
  • Нет необходимости в каких-либо специальных «плохих»input "code - компилятор обрабатывает это для вас
  • Enum значения уникальны
10 голосов
/ 13 февраля 2012

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

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

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

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

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

10 голосов
/ 13 февраля 2012

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

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

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

7 голосов
/ 13 февраля 2012

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

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

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

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

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

3 голосов
/ 06 января 2015

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

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

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

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;
    }
}
3 голосов
/ 13 февраля 2012

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

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

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

...

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

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

2 голосов
/ 15 ноября 2014

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

import java.util.Random;

открытый класс switchTest { публичное перечисление MyEnum { Значение1, Значение2, Значение3, Значение4, Значение5 };

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

целых: 362

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

2 голосов
/ 13 февраля 2012

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

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

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

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

0 голосов
/ 06 сентября 2014

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

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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...