【问题标题】:Java: Enum vs. IntJava:枚举与整数
【发布时间】:2012-03-04 12:10:57
【问题描述】:

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

我想知道使用枚举和整数作为标志在内存使用和速度方面是否存在差异?

【问题讨论】:

标签: java enums


【解决方案1】:

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

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

首选枚举。

【讨论】:

  • 是的,枚举更有条理。
  • 我会说int的使用主要是因为Java 1.4没有enum
  • 同意。我认为这是正确的。但我们在这里讨论的是如何对新开发做出决定。
【解决方案2】:

请记住,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 提供的类型安全性使得最小开销可以接受。

【讨论】:

    【解决方案3】:

    intsenums 都可以同时使用 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); - 再次昂贵
    • 没有什么可以防止常量冲突——即使YELLOWGREEN具有相同的值3,上面的类代码也会编译

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

    • 除非您传入有效值,否则您的代码将无法编译
    • 不需要任何特殊的“错误输入”代码 - 编译器会为您处理
    • 枚举值是唯一的

    【讨论】:

    • 很好地把答案放在一起,并且解释得很清楚。谢谢。
    • the above class code will compile even though YELLOW and GREEN both have the same value 3。枚举也是如此。我可以将 3 作为 YELLOW 和 GREEN 的状态。那么,它有什么优势呢?
    • @Breaking with an enum,没有3。您使用enum 而不是 int。即实例字段是private Color color;,方法是public void setColor(Color color) {this.color = color;}。如果枚举本身有一个字段,即使两个枚举实例对该字段具有相同的值,这与 API 的安全性无关,它只接受一个枚举实例。
    • “不需要任何特殊的“错误输入”代码 - 编译器会为您处理 ...是也不是。与ints 不同,引用类型(例如作为枚举)可以是null。使用int 值的代码不需要空处理。使用枚举的代码将需要空处理(除非可以证明该值永远不能为空)。跨度>
    • @scott true,但空值检查是您可能需要的唯一检查,虽然整数不能为空,但它们有数十亿个无意义的(在这种情况下)价值观
    【解决方案4】:

    您甚至可以使用枚举来替换那些按位组合的标志,例如 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 的标志。

    【讨论】:

    • 这也适用于Android吗? (关于效率)文档说它是从 1.5 版本开始的,所以他们应该......
    【解决方案5】:

    您会看到一些使用int 标志而不是enum 的代码的原因之一是Java 在Java 1.5 之前没有枚举

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

    在现代 Java 代码中,在极少数地方使用 int 标志仍然更可取,但在大多数情况下,您应该更喜欢使用 enum,因为它们提供了类型安全性和表现力。

    就效率而言,这将取决于它们的使用方式。 JVM 可以非常有效地处理这两种类型,但是对于某些用例,int 方法可能会稍微更有效(因为它们被作为原始而不是对象处理),但在其他情况下,枚举会更有效(因为它没有t需要去扔拳击/拆箱)。

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

    【讨论】:

    • 需要注意的是,enum 在 Java 5+ 中的相同效果可以在早期版本中通过手动创建一个类似枚举的类(使用私有构造函数,一些static final 字段和适当的序列化魔法以确保唯一性)。它很少使用非常,但谷歌搜索“类型安全枚举 Java 1.4”可能会带来一两个教程。 enum 是“只是”它的语法糖。
    【解决方案6】:

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

    正如其他人所说,这两种类型都可以在 switch 或 if else 语句中使用。此外,正如其他人所说,您应该更喜欢 Enums 而不是 int 标志,因为它们旨在替换该模式并且它们提供了额外的安全性。

    但是,您可以考虑一种更好的模式。提供你的 switch 语句/if 语句应该作为属性产生的任何值。

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

    【讨论】:

    • Java 枚举不是整数......它们是面向对象的类。 C 枚举和 Java 枚举之间有一条细线。
    • 关于 int 的部分实际上是它们如何实现 EnumSets。 Enum 是一个完整的类这一事实反映在我的回答中,它鼓励将 Enum 视为一个类而不是使用 switch 语句。
    【解决方案7】:

    尽管这个问题很老,但我想指出你不能用 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);
    

    【讨论】:

    • 虽然这并不能真正回答问题,但它涉及两种技术的运行时性能
    【解决方案8】:

    我喜欢在可能的情况下使用枚举,但是我有一种情况,我必须为我在枚举中定义的不同文件类型计算数百万个文件偏移量,并且我必须执行数千万次 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 相比,第一次使用字符串和枚举存在一些开销。

    【讨论】:

    • 示例中的 MyEnum.values() 在每次迭代时制作一个数组副本。效率不高...
    【解决方案9】:

    是的,有区别。在现代 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;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-20
      • 1970-01-01
      • 2016-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多