简体   繁体   English

Java:Enum vs. Int

[英]Java: Enum vs. Int

When using flags in Java, I have seen two main approaches. 在Java中使用标志时,我看到了两种主要方法。 One uses int values and a line of if-else statements. 一个使用int值和一行if-else语句。 The other is to use enums and case-switch statements. 另一种是使用枚举和case-switch语句。

I was wondering if there was a difference in terms of memory usage and speed between using enums vs ints for flags? 我想知道在使用enums和ints for flags之间内存使用和速度方面是否存在差异?

Both ints and enums can use both switch or if-then-else, and memory usage is also minimal for both, and speed is similar - there's no significant difference between them on the points you raised. intsenums都可以同时使用switch或if-then-else,两者的内存使用量也是最小的,速度也很相似 - 它们在你提出的点上没有显着差异。

However, the most important difference is the type checking. 但是,最重要的区别是类型检查。 Enums are checked, ints are not. Enums检查, ints都没有。

Consider this code: 考虑以下代码:

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;
    }   
}

While many clients will use this properly, 虽然许多客户会正确使用这个,

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

There is nothing stopping them from writing this: 没有什么可以阻止他们写这个:

new SomeClass().setColor(999);

There are three main problems with using the public static final pattern: 使用public static final模式有三个主要问题:

  • The problem occurs at runtime , not compile time, so it's going to be more expensive to fix, and harder to find the cause 问题发生在运行时 ,而不是编译时,因此修复起来会更加昂贵,并且更难找到原因
  • You have to write code to handle bad input - typically a if-then-else with a final else throw new IllegalArgumentException("Unknown color " + color); 你必须编写代码来处理错误的输入 - 通常是if-then-else ,最后的else throw new IllegalArgumentException("Unknown color " + color); - again expensive - 又贵了
  • There is nothing preventing a collision of constants - the above class code will compile even though YELLOW and GREEN both have the same value 3 没有什么能阻止常量的冲突 - 即使YELLOWGREEN都具有相同的值3 ,上面的类代码也会编译

If you use enums , you address all these problems: 如果您使用enums ,则可以解决所有这些问题:

  • Your code won't compile unless you pass valid values in 除非您传入有效值,否则您的代码将无法编译
  • No need for any special "bad input" code - the compiler handles that for you 不需要任何特殊的“错误输入”代码 - 编译器会为您处理
  • Enum values are unique 枚举值是唯一的

Memory usage and speed aren't the considerations that matter. 内存使用和速度不是重要的考虑因素。 You would not be able to measure a difference either way. 你无论如何都无法衡量差异。

I think enums should be preferred when they apply, because the emphasize the fact that the chosen values go together and comprise a closed set. 我认为枚举在应用时应该是首选的,因为强调所选择的值组合在一起并构成一个封闭的集合。 Readability is improved a great deal, too. 可读性也得到了很大改善。 Code using enums is more self-documenting than stray int values scattered throughout your code. 使用枚举的代码比分散在整个代码中的杂散int值更自我记录。

Prefer enums. 喜欢枚举。

You may even use Enums to replace those bitwise combined flags like int flags = FLAG_1 | FLAG_2; 您甚至可以使用Enums来替换那些按位组合标志,例如int flags = FLAG_1 | FLAG_2; int flags = FLAG_1 | FLAG_2;

Instead you can use a typesafe EnumSet : 相反,您可以使用类型安全的EnumSet

Set<FlagEnum> flags = EnumSet.of(FlagEnum.FLAG_1, FlagEnum.FLAG_2);

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

The documentation states that those classes are internally optimized as bit vectors and that the implementation should be perform well enough to replace the int-based flags. 文档声明这些类在内部被优化为位向量,并且实现应该足够好以替换基于int的标志。

One of the reasons you will see some code using int flags instead of an enum is that Java did not have enums until Java 1.5 您将看到使用int标志而不是enum一些代码的原因之一是Java在Java 1.5之前没有枚举

So if you are looking at code that was originally written for an older version of Java, then the int pattern was the only option available. 因此,如果您正在查看最初为旧版Java编写的代码,那么int模式是唯一可用的选项。

There are a very small number of places where using int flags is still preferable in modern Java code, but in most cases you should prefer to use an enum , due to the type safety and expressiveness that they offer. 在现代Java代码中,使用int标志的情况仍然很少,但在大多数情况下,由于它们提供的类型安全性和表现力,您应该更喜欢使用enum

In terms of efficiency, it will depend on exactly how they are used. 在效率方面,它将取决于它们的确切使用方式。 The JVM handles both types very efficiently, but the int method would likely be slightly more efficient for some use cases (because they are handled as primitive rather than objects), but in other cases, the enum would be more efficient (because it doesn't need to go throw boxing/unboxing). JVM非常有效地处理这两种类型,但对于某些用例,int方法可能稍微有效(因为它们被处理为原始而不是对象),但在其他情况下,枚举会更有效(因为它不会'需要去扔拳击/拆箱)。

You would be hard pressed to find a situation in which the efficiency difference would be in any way noticeable in a real world application , so you should make the decision based on the quality of the code (readability and safety), which should lead you to use an enum 99% of the time . 您很难找到效率差异在现实世界的应用程序中以任何方式显而易见的情况 ,因此您应该根据代码的质量(可读性和安全性)做出决定,这将导致您99%的时间使用枚举

Yes, there is a difference. 是,有一点不同。 Under modern 64-bit java Enum values are essentially pointers to objects and they either take 64 bits (non-compressed ops) or use additional CPU (compressed ops). 在现代64位java Enum值下,本质上是指向对象的指针,它们要么采用64位(非压缩操作),要么使用额外的CPU(压缩操作)。

My test showed about 10% performance degradation for enums (1.8u25, AMD FX-4100): 13k ns vs 14k ns 我的测试显示,枚举(1.8u25,AMD FX-4100)性能下降约10%:13k ns vs 14k ns

Test source below: 测试来源如下:

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;
    }
}

Bear in mind that enums are type-safe, and you can't mix values from one enum with another. 请记住, enums是类型安全的,您不能将一个枚举的值与另一个枚举混合。 That's a good reason to prefer enums over ints for flags. 这是一个很好的理由,更喜欢enumsints的标志。

On the other hand, if you use ints for your constants, you can mix values from unrelated constants, like this: 另一方面,如果对常量使用ints ,则可以混合不相关常量的值,如下所示:

public static final int SUNDAY = 1;
public static final int JANUARY = 1;

...

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

The memory usage of enums over ints is negligible, and the type safety enums provide makes the minimal overhead acceptable. 的存储器使用enumsints 可以忽略的,以及安全性的类型enums提供使得最小的开销可以接受的。

I like using Enums when possible but I had a situation where I was having to compute millions of file offsets for different file types which I had defined in an enum and I had to execute a switch statement tens of millions of times to compute the offset base on on the enum type. 我喜欢在可能的情况下使用Enums,但我遇到的情况是我必须为枚举中定义的不同文件类型计算数百万个文件偏移量而且我必须执行一个数百万次的switch语句来计算偏移量关于枚举类型。 I ran the following test: 我运行了以下测试:

import java.util.Random;

public class switchTest { public enum MyEnum { Value1, Value2, Value3, Value4, Value5 }; 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));
}

} }

and got the following results: 并得到以下结果:

strings:442 串:442

enums :455 枚举:455

ints :362 整数:362

So from this I decided that for me enums were efficient enough. 因此我决定对我来说enum足够有效。 When I decreased the loop counts to 1M from 10M the string and enums took about twice as long as the int which indicates that there was some overhead to using strings and enums for the first time as compared to ints. 当我将循环计数从10M减少到1M时,字符串和枚举所用的时间大约是int的两倍,这表明与int相比,第一次使用字符串和枚举有一些开销。

Answer to your question: No, the after a negligible time to load the Enum Class, the performance is the same. 回答你的问题:不,在加载Enum Class之后可以忽略不计的时间,性能是一样的。

As others have stated both types can be used in switch or if else statements. 正如其他人所说,这两种类型都可以用于switch或if else语句。 Also, as others have stated, you should favor Enums over int flags, because they were designed to replace that pattern and they provide added safety. 此外,正如其他人所说,你应该赞成Enums而不是int标志,因为它们旨在取代这种模式,并且它们提供了额外的安全性。

HOWEVER, there is a better pattern that you consider. 但是,你考虑的是更好的模式。 Providing whatever value your switch statement/if statement was supposed to produce as property. 提供switch语句/ if语句应该作为属性生成的任何值。

Look at this link: http://docs.oracle.com/javase/1.5.0/docs/guide/language/enums.html Notice the pattern provided for giving the planets masses and radii. 请看这个链接: http//docs.oracle.com/javase/1.5.0/docs/guide/language/enums.html注意提供行星质量和半径的模式。 Providing the property in this manner insures that you won't forget to cover a case if you add an enum. 以这种方式提供财产可确保您在添加枚举时不会忘记覆盖案例。

Even though this question is old, I'd like to point out what you can't do with ints 尽管这个问题已经过时了,但我想指出一下你不能用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) {
            .....}}
}

And what you can do is make a map of what value is expected as a Key, and the enum as a value, 你可以做的是制作一个关于Key的预期值的映射,以及作为值的枚举,

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM