Java: Enum vs. Int
在爪哇使用标志时,我看到了两种主要方法。一个使用int值和一行if-else语句。另一种方法是使用枚举和case switch语句。
我想知道在使用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); |
使用
- 该问题发生在运行时,而不是编译时,因此修复成本更高,更难找到原因。
- 您必须编写代码来处理错误的输入——通常是
if-then-else 和最终的else throw new IllegalArgumentException("Unknown color" + color); ——同样昂贵 - 没有什么可以防止常量冲突的——即使
YELLOW 和GREEN 的值相同,上述类代码也会编译。
如果您使用
- 除非在
- 不需要任何特殊的"错误输入"代码-编译器会为您处理这些代码
- 枚举值是唯一的
甚至可以使用枚举来替换那些按位组合的标志,如
相反,您可以使用类型安全枚举:
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; } } |
记住,
另一方面,如果将
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; |
我喜欢尽可能使用枚举,但我遇到了这样一种情况:我必须为在枚举中定义的不同文件类型计算数百万个文件偏移量,我必须执行一个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 |