【发布时间】:2012-03-04 12:10:57
【问题描述】:
在 Java 中使用标志时,我看到了两种主要方法。一个使用 int 值和一行 if-else 语句。另一种是使用枚举和case-switch语句。
我想知道使用枚举和整数作为标志在内存使用和速度方面是否存在差异?
【问题讨论】:
在 Java 中使用标志时,我看到了两种主要方法。一个使用 int 值和一行 if-else 语句。另一种是使用枚举和case-switch语句。
我想知道使用枚举和整数作为标志在内存使用和速度方面是否存在差异?
【问题讨论】:
内存使用和速度并不是重要的考虑因素。无论哪种方式,您都无法衡量差异。
我认为枚举在应用时应该是首选,因为它强调了所选值组合在一起并构成一个封闭集的事实。可读性也大大提高了。使用枚举的代码比散布在代码中的杂散 int 值更具自我记录性。
首选枚举。
【讨论】:
int的使用主要是因为Java 1.4没有enum。
请记住,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 提供的类型安全性使得最小开销可以接受。
【讨论】:
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,则可以解决所有这些问题:
【讨论】:
the above class code will compile even though YELLOW and GREEN both have the same value 3。枚举也是如此。我可以将 3 作为 YELLOW 和 GREEN 的状态。那么,它有什么优势呢?
enum,没有3。您使用enum 而不是 int。即实例字段是private Color color;,方法是public void setColor(Color color) {this.color = color;}。如果枚举本身有一个字段,即使两个枚举实例对该字段具有相同的值,这与 API 的安全性无关,它只接受一个枚举实例。
ints 不同,引用类型(例如作为枚举)可以是null。使用int 值的代码不需要空处理。使用枚举的代码将需要空处理(除非可以证明该值永远不能为空)。跨度>
您甚至可以使用枚举来替换那些按位组合的标志,例如 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 的标志。
【讨论】:
您会看到一些使用int 标志而不是enum 的代码的原因之一是Java 在Java 1.5 之前没有枚举
因此,如果您正在查看最初为旧版 Java 编写的代码,那么 int 模式是唯一可用的选项。
在现代 Java 代码中,在极少数地方使用 int 标志仍然更可取,但在大多数情况下,您应该更喜欢使用 enum,因为它们提供了类型安全性和表现力。
就效率而言,这将取决于它们的使用方式。 JVM 可以非常有效地处理这两种类型,但是对于某些用例,int 方法可能会稍微更有效(因为它们被作为原始而不是对象处理),但在其他情况下,枚举会更有效(因为它没有t需要去扔拳击/拆箱)。
您很难找到在实际应用程序中效率差异会以任何方式明显的情况,因此您应该根据代码的质量做出决定(可读性和安全性),这应该会导致您在 99% 的时间内使用枚举。
【讨论】:
enum 在 Java 5+ 中的相同效果可以在早期版本中通过手动创建一个类似枚举的类(使用私有构造函数,一些static final 字段和适当的序列化魔法以确保唯一性)。它很少使用非常,但谷歌搜索“类型安全枚举 Java 1.4”可能会带来一两个教程。 enum 是“只是”它的语法糖。
回答你的问题:不,在加载 Enum 类的时间可以忽略不计之后,性能是相同的。
正如其他人所说,这两种类型都可以在 switch 或 if else 语句中使用。此外,正如其他人所说,您应该更喜欢 Enums 而不是 int 标志,因为它们旨在替换该模式并且它们提供了额外的安全性。
但是,您可以考虑一种更好的模式。提供你的 switch 语句/if 语句应该作为属性产生的任何值。
查看此链接:http://docs.oracle.com/javase/1.5.0/docs/guide/language/enums.html 请注意提供给行星质量和半径的模式。以这种方式提供属性可确保您在添加枚举时不会忘记覆盖案例。
【讨论】:
尽管这个问题很老,但我想指出你不能用 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);
【讨论】:
我喜欢在可能的情况下使用枚举,但是我有一种情况,我必须为我在枚举中定义的不同文件类型计算数百万个文件偏移量,并且我必须执行数千万次 switch 语句来计算基于枚举类型的偏移量。我进行了以下测试:
import java.util.Random;
公共类 switchTest { 公共枚举 MyEnum { 值 1、值 2、值 3、值 4、值 5 };
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
因此,我认为枚举对我来说足够高效。当我将循环计数从 10M 减少到 1M 时,字符串和枚举所用的时间大约是 int 的两倍,这表明与 int 相比,第一次使用字符串和枚举存在一些开销。
【讨论】:
是的,有区别。在现代 64 位 Java 枚举值本质上是指向对象的指针,它们要么占用 64 位(非压缩操作),要么使用额外的 CPU(压缩操作)。
我的测试显示枚举(1.8u25,AMD FX-4100)的性能下降了大约 10%: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;
}
}
【讨论】: