在Java中使用标志时,我看到了两种主要方法。一个使用int值和一行if-else语句。另一种是使用枚举和case-switch语句。
我想知道在使用enums和ints for flags之间内存使用率和速度方面是否存在差异?
答案 0 :(得分:122)
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
,则可以解决所有这些问题:
答案 1 :(得分:10)
内存使用和速度不是重要的考虑因素。无论如何,你都无法衡量差异。
我认为枚举在应用时应该是首选,因为它强调所选值组合在一起并构成一个封闭集合的事实。可读性也得到了很大改善。使用枚举的代码比分散在整个代码中的杂散int值更自我记录。
首选枚举。
答案 2 :(得分:10)
您甚至可以使用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的标志。
答案 3 :(得分:7)
您将看到使用int
标志而不是enum
的代码的一个原因是Java在Java 1.5之前没有枚举
因此,如果您正在查看最初为旧版Java编写的代码,那么int
模式是唯一可用的选项。
在现代Java代码中,使用int
标志的地方数量非常少,但在大多数情况下,由于类型安全和表现力,您应该更喜欢使用enum
他们提供的。
在效率方面,它将取决于它们的确切使用方式。 JVM非常有效地处理这两种类型,但对于某些用例,int方法可能稍微有效(因为它们被处理为原始而不是对象),但在其他情况下,枚举会更有效(因为它不会'需要去扔拳击/拆箱)。
您很难找到效率差异在现实世界的应用程序中以任何方式显着的情况,因此您应该根据代码的质量做出决定(可读性和安全性,这将导致您<99>在99%的时间内使用枚举。
答案 4 :(得分: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
提供的类型使得最小开销可以接受。
答案 5 :(得分:3)
是的,有区别。在现代64位java Enum值下,它实际上是指向对象的指针,它们要么采用64位(非压缩操作),要么使用额外的CPU(压缩操作)。
我的测试表明,枚举性能下降了10%(1.8u25,AMD FX-4100):13k ns vs 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;
}
}
答案 6 :(得分:2)
回答你的问题:不,在加载Enum Class之后可以忽略不计的时间,性能是一样的。
正如其他人所说,这两种类型都可用于switch或if else语句。此外,正如其他人所说,你应该赞成Enums而不是int标志,因为它们旨在取代这种模式,并且它们提供了额外的安全性。
但是,您考虑的是更好的模式。提供switch语句/ if语句应该作为属性产生的任何值。
请看这个链接:http://docs.oracle.com/javase/1.5.0/docs/guide/language/enums.html注意为行星质量和半径提供的模式。以这种方式提供财产可确保您在添加枚举时不会忘记覆盖案例。
答案 7 :(得分:2)
我喜欢在可能的情况下使用Enums但我遇到的情况是我必须为枚举中定义的不同文件类型计算数百万个文件偏移量而且我必须执行数千万次的switch语句来计算基于枚举类型的偏移量。我运行了以下测试:
import java.util.Random;
public class switchTest { public enum 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
因此我决定对我来说enum足够有效。当我将循环计数从10M减少到1M时,字符串和枚举大约是int的两倍,这表明与int相比,首次使用字符串和枚举有一些开销。
答案 8 :(得分:0)
尽管这个问题已经过时了,但我想指出一下你可以用int来做什么
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) {
.....}}
}
你可以做的是制作一张关于Key的预期价值的地图,以及作为价值的枚举
Map<String, AttributeProcessor> map
map.getOrDefault(key, ParseArrayEnd.State1).process(this, c);