Ява: перечислимые и Инт
при использовании флагов в Java, я видел два основных подхода. Один использует значения int и строку операторов if-else. Другой заключается в использовании перечислений и операторы Switch.
Мне было интересно, есть ли разница в использовании памяти и скорости между использованием enums vs ints для флагов?
9 ответов:
и
intsиenumsможно использовать как переключатель, так и 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шаблон:
- проблема возникает в runtime, а не compile время, так что это будет дороже исправить, и труднее найти причину
- вы должны написать код для обработки плохого ввода-обычно a
if-then-elseс окончательнымelse throw new IllegalArgumentException("Unknown color " + color);- опять дорого- ничто не мешает столкновению константы-приведенный выше код класса будет компилироваться, даже если
YELLOWиGREENоба имеют одинаковое значение3если вы используете
enums, вы решаете все эти проблемы:
- ваш код не будет компилироваться, если вы не передадите допустимые значения в
- нет необходимости в каком - либо специальном "плохом входном" коде-компилятор обрабатывает это для вас
- значения перечисления уникальны
вы даже можете использовать перечисления для замены этих побитовых комбинированных флагов, таких как
int flags = FLAG_1 | FLAG_2;вместо этого вы можете использовать typesafe EnumSet:
Set<FlagEnum> flags = EnumSet.of(FlagEnum.FLAG_1, FlagEnum.FLAG_2); // then simply test with contains() if(flags.contains(FlagEnum.FLAG_1)) ...в документации говорится, что эти классы внутренне оптимизированы как битовые векторы и что реализация должна быть выполнена достаточно хорошо, чтобы заменить флаги на основе int.
использование памяти и скорость не являются соображениями, которые имеют значение. Вы не сможете измерить разницу в любом случае.
Я думаю, что перечисления должны быть предпочтительными, когда они применяются, потому что подчеркивают тот факт, что выбранные значения идут вместе и составляют замкнутый набор. Читаемость также значительно улучшена. Код, использующий перечисления, более самодокументирован, чем случайные значения int, разбросанные по всему коду.
предпочитаю перечислений.
одна из причин, по которой вы увидите некоторый код с помощью
intфлаги вместоenumэто то, что Java не было перечислений до Java 1.5так что если вы смотрите на код, который был изначально написан для старой версии Java, то
intшаблон был единственным доступным вариантом.есть очень небольшое количество мест, где с помощью
intфлаги по-прежнему предпочтительнее в современном Java-коде, но в большинстве случаев вы должны предпочесть использоватьenum, в связи с тип безопасности и выразительности, которые они предлагают.С точки зрения эффективности, это будет зависеть от того, как именно они используются. JVM обрабатывает оба типа очень эффективно, но метод int, вероятно, будет немного более эффективным для некоторых случаев использования (потому что они обрабатываются как примитивные, а не объекты), но в других случаях перечисление будет более эффективным (потому что ему не нужно бросать бокс/распаковку).
Вы были бы в затруднении, чтобы найти положение в котором разница в эффективности была бы в любом случае заметна в реальном приложении, поэтому вы должны принять решение, основанное на качестве кода (читаемость и безопасность), который должен привести вас к используйте перечисление 99% времени.
имейте в виду, что
enumsявляются типобезопасными, и вы не можете смешивать значения из одного перечисления с другим. Это хорошая причина, чтобы предпочестьenumsoverintsдля флагов.С другой стороны, если вы используете
intsдля ваших констант вы можете смешивать значения из несвязанных констант, например:public static final int SUNDAY = 1; public static final int JANUARY = 1; ... // even though this works, it's a mistake: int firstMonth = SUNDAY;использование памяти
enumsoverintsи незначительно, и тип безопасностиenumsprovide делает минимальные накладные расходы приемлемыми.
ответ на ваш вопрос: нет, после незначительного времени для загрузки класса Enum производительность одинакова.
Как заявили другие, оба типа могут использоваться в операторах switch или if else. Кроме того, как заявили другие, вы должны отдавать предпочтение перечислениям над флагами int, потому что они были разработаны для замены этого шаблона, и они обеспечивают дополнительную безопасность.
однако, есть лучший шаблон, который вы считаете. Предоставление любого значения вашего оператора switch / if предполагалось производить как собственность.
мне нравится использовать перечисления, когда это возможно, но у меня была ситуация, когда мне приходилось вычислять миллионы смещений файлов для разных типов файлов, которые я определил в перечислении, и мне пришлось выполнить оператор switch десятки миллионов раз, чтобы вычислить базу смещения на типе перечисления. Я провел следующий тест:
import java.util.Random;общественный класс switchTest { общественное перечисление 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
Да, есть разница. В современных 64-разрядных Java Enum значения по существу являются указателями на объекты, и они либо принимают 64 бита (несжатые ops), либо используют дополнительный процессор (сжатые ops).
мой тест показал около 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; } }
хотя этот вопрос старый, я хотел бы указать, что вы не можете сделать с 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);
Comments