Java:Enum vs. Int

Java: Enum vs. Int

在爪哇使用标志时,我看到了两种主要方法。一个使用int值和一行if-else语句。另一种方法是使用枚举和case switch语句。

我想知道在使用Enums和Ints作为标记之间,内存使用率和速度是否存在差异?


intsenums都可以同时使用两个开关,或者如果使用其他开关,那么内存使用量对这两个开关来说也是最小的,并且速度是相似的——它们在您提出的点上没有显著的差异。

但是,最重要的区别是类型检查。检查enums,不检查ints

考虑此代码:

1
2
3
4
5
6
7
8
9
10
11
12
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;
    }  
}

虽然许多客户会正确使用它,

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

没有什么能阻止他们这样写:

1
new SomeClass().setColor(999);

使用public static final模式有三个主要问题:

  • 该问题发生在运行时,而不是编译时,因此修复成本更高,更难找到原因。
  • 您必须编写代码来处理错误的输入——通常是if-then-else和最终的else throw new IllegalArgumentException("Unknown color" + color);——同样昂贵
  • 没有什么可以防止常量冲突的——即使YELLOWGREEN的值相同,上述类代码也会编译。

如果您使用enums,则可以解决所有这些问题:

  • 除非在
  • 不需要任何特殊的"错误输入"代码-编译器会为您处理这些代码
  • 枚举值是唯一的


甚至可以使用枚举来替换那些按位组合的标志,如int flags = FLAG_1 | FLAG_2;

相反,您可以使用类型安全枚举:

1
2
3
4
Set<FlagEnum> flags = EnumSet.of(FlagEnum.FLAG_1, FlagEnum.FLAG_2);

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

文档声明这些类在内部作为位向量进行了优化,并且实现的性能应该足以替换基于int的标志。


内存使用和速度不是重要的考虑因素。无论哪种方法,您都无法测量差异。

我认为枚举在应用时应该是首选的,因为它强调了这样一个事实:所选的值组合在一起并构成一个封闭集。可读性也大大提高了。使用枚举的代码比分散在代码中的零散int值更能自我记录。

喜欢枚举。


使用EDCOX1的19个标志而不是EDCOX1的20个代码来查看一些代码的原因之一是Java在Java 1.5之前没有枚举。

因此,如果您查看的代码最初是为旧版本的Java编写的,那么EDCOX1×19的模式是唯一可用的选项。

在现代Java代码中,使用EDCOX1或19个标志的地方非常少,但在大多数情况下,由于它们提供的类型安全性和表达性,您应该更喜欢使用EDCOX1×20。

在效率方面,这将完全取决于它们是如何使用的。JVM可以非常高效地处理这两种类型,但是对于某些用例,in t方法可能会稍微高效一些(因为它们是作为原语而不是对象来处理的),但是在其他情况下,枚举会更高效(因为它不需要进行装箱/拆箱)。

在实际应用中,很难找到效率差异在任何方面都会显著的情况,因此您应该根据代码的质量(可读性和安全性)做出决定,这将导致您使用99%的枚举。


是的,有区别。在现代的64位Java枚举值中,基本上是指向对象的指针,它们要么取64位(非压缩的OPS),要么使用额外的CPU(压缩的OPS)。

我的测试显示,Enums(1.8u25,AMD FX-4100)的性能下降了约10%:13k ns vs 14k ns

测试源如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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;
    }
}

记住,enums是类型安全的,不能将一个枚举的值与另一个枚举的值混合。这是一个很好的理由来选择enums而不是ints作为标记。

另一方面,如果将ints用于常量,则可以混合来自无关常量的值,如下所示:

1
2
3
4
5
6
7
public static final int SUNDAY = 1;
public static final int JANUARY = 1;

...

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

enumsints的内存使用可以忽略不计,类型安全enums提供了可接受的最小开销。


我喜欢尽可能使用枚举,但我遇到了这样一种情况:我必须为在枚举中定义的不同文件类型计算数百万个文件偏移量,我必须执行一个switch语句几千万次才能基于枚举类型计算偏移量。我进行了以下测试:

1
import java.util.Random;

公共类开关测试{公共枚举MyEnum{值1、值2、值3、值4、值5};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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

因此,我认为对我来说,枚举是足够有效的。当我将循环计数从10米减少到1米时,字符串和枚举所用的时间大约是int的两倍,这表明与int相比,首次使用字符串和枚举有一些开销。


回答您的问题:不,在加载枚举类的时间可以忽略不计之后,性能是相同的。

正如其他人所说,这两种类型都可以在switch或if else语句中使用。另外,正如其他人所说,您应该倾向于使用枚举而不是int标志,因为它们是为替换该模式而设计的,并且提供了额外的安全性。

然而,有一个更好的模式,你考虑。提供应该作为属性生成的switch语句/if语句的任何值。

查看这个链接:http://docs.oracle.com/javase/1.5.0/docs/guide/language/enums.html注意提供行星质量和半径的模式。以这种方式提供属性可以确保在添加枚举时不会忘记覆盖案例。


尽管这个问题很古老,但我想指出你不能用ints做什么

1
2
3
4
5
6
7
8
9
10
11
12
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) {
            .....}}
}

你所能做的就是做一个作为键期望值的映射,以及作为值的枚举,

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