2012-02-13 2 views
50

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

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

+0

Связанные вопрос: http://stackoverflow.com/questions/4709175/why-and-what-for- java-enum/4709224 # 4709224 – sleske

ответ

112

Оба ints и enums могут использовать оба переключателя или если-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, вы решить все эти проблемы:

  • Ваш код не будет компилироваться, если вы не пройдете допустимые значения в
  • Нет необходимости в каких-либо специальных «плохого ввода» кода - Рукоятки компилятор что для вас
  • значения перечислений являются уникальными
+0

Хорошо собрал ответ, и объясняет это очень четко. Спасибо. – Kervvv

8

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

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

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

+0

Да, перечисления намного более организованы. – Jimmt

+1

Я бы сказал, что использование 'int' происходит в основном из-за того, что Java 1.4 не имеет' enum'. – skolima

+1

Согласовано. Я думаю, это правильно. Но мы говорим о том, как принять решение о новой разработке здесь. – duffymo

3

Имейте в виду, что 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 обеспечивают минимальные накладные расходы.

10

Вы можете даже использовать перечисления заменить эти побитовые объединенные флаги как 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)) ... 

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

+0

Это относится и к Android? (об эффективности). Документы говорят, что это с версии 1.5, поэтому они должны ... –

4

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

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

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

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

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

+2

Следует отметить, что тот же эффект, что 'enum' в Java 5+ может быть достигнут в более ранних версиях вручную *, создавая enum- (с частным конструктором, некоторыми «статическими конечными» полями и подходящей магией сериализации для обеспечения уникальности). Он использовался * очень редко, но googling для «безопасного типа enum Java 1.4», вероятно, откроет учебник или два. 'enum' является« просто »синтаксическим сахаром для этого. –

2

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

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

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

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

+0

Переменные Java не являются ints ... они являются объектно-ориентированными классами. Существует тонкая грань между перечислениями C и перечислениями Java. – GGulati

+0

Часть о ints на самом деле как они реализуют EnumSets. Тот факт, что Enum является полным классом, отражен в моем ответе, который поощряет обработку Enum как класса с использованием операторов switch. – Joe

0

Хотя этот вопрос стар, я хотел бы указать на то, что вы не можете сделать с Интс

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); 
+0

Хотя это правда, на самом деле это не отвечает на вопрос, касающийся производительности _runtime_ двух методов. – phs

2

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

import java.util.Random; 

общественного класса switchTest { общественное перечисление MyEnum { Значение1, значение2, 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

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

+0

MyEnum.values ​​() в вашем примере делает копию массива на каждой итерации. Не слишком эффективно ... –

2

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

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

источник

испытаний ниже:

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; 
    } 
}