Search code examples
javaenumscompiler-construction

How clever java compiler is? Issues about enum method


Consider this situation:

Style1:

static enum Style1{
    FIRE_BALL {
        @Override
        public boolean isCold() {
            return false;
        }
    },ICE_BALL {
        @Override
        public boolean isCold() {
            return true;
        }
    },FIRE_ARROW {
        @Override
        public boolean isCold() {
            return false;
        }
    },ICE_ARROW {
        @Override
        public boolean isCold() {
            return true;
        }
    };
    public abstract boolean isCold();
}

Style2:

static enum Style2{
    FIRE_BALL,ICE_BALL,FIRE_ARROW,ICE_ARROW;
    public boolean isCold(){
        //return this.toString().contains("ICE")?true:false; //sorry
        return this.toString().contains("ICE");
    }
}

Now, I simply want to know whether it's cold. So I'm going to ask:

Can compiler know the destined results and constant-fold Style2 ?

If not, Style1 should be obviously faster, but more verbose. Assume this were more complicated situation and there were many more combinations, such as BIG_FIRE_SLOW_BALL with isFast(), isBig(), Style1 will end up with chunks of codes.


So I did some tests with jmh and jUnit:

1.with jmh:

@Benchmark
public boolean testStyle1() {
    return Style1.values()[ThreadLocalRandom.current().nextInt(4)].isCold();
}

@Benchmark
public boolean testStyle2() {
    return Style2.values()[ThreadLocalRandom.current().nextInt(4)].isCold();
}

when setting:

            .warmupIterations(10)
            .measurementIterations(10)
            .threads(8)

Benchmark             Mode  Cnt   Score   Error  Units
EnumTest1.testStyle1  avgt   10  34.057 ± 0.101  ns/op
EnumTest1.testStyle2  avgt   10  36.196 ± 0.453  ns/op

well, set thread number to 1

            .threads(1)
Benchmark             Mode  Cnt   Score    Error  Units
EnumTest1.testStyle1  avgt   10  34.306 ± 11.692  ns/op
EnumTest1.testStyle2  avgt   10  44.279 ± 11.103  ns/op

So, it seems like Style2 can't be optimized by compiler.

2,with jUnit:

private static final int LOOP_TIMES = 100000000;
private static final Random random1=new Random(47);
private static final Random random2=new Random(47);

@Test
public void testStyle1() {
    int cnt = 0;
    for (int i = 0; i < LOOP_TIMES; i++) {
        if(Style1.values()[random1.nextInt(4)].isCold()){
            cnt++;
        }
    }
    System.out.println(cnt);
}

@Test
public void testStyle2() {
    int cnt = 0;
    for (int i = 0; i < LOOP_TIMES; i++) {
        if(Style2.values()[random2.nextInt(4)].isCold()){
            cnt++;
        }
    }
    System.out.println(cnt);
}

results:

Time:      1       2       3    inverse order   4      5       6
Style1: 3.631s  4.578s  3.754s    Style2     4.131s  5.487s  4.261s  
Style2: 2.559s  4.216s  3.155s    Style1     2.316s  3.977s  4.152s

So, Style1 is likely to be faster.


But why two results are close, especially when I concurrently do the test with jmh? Or how indeed should we deal with this?

Maybe giving Style1 some fields to store each one's result could make it less redundant. But I still feel not very satisfied. Hope some of you could tell me more.


Thank you guys very much. @Andy gave a very good example, I add it here:

enum Style4{
    FIRE_BALL,
    ICE_BALL,
    FIRE_ARROW,
    ICE_ARROW;

    private boolean cold;

    private Style4(){
        this.cold = this.toString().contains("ICE");
    }

    public boolean isCold(){
        return cold;
    }
}

This fourth style works without mentioning true or false.


Solution

  • You can improve on the verbosity of style1 somewhat if you use constructors. This will be fast, and (in my opinion) easier to read.

    enum Style1{
        FIRE_BALL(false),
        ICE_BALL(true),
        FIRE_ARROW(false),
        ICE_ARROW(true);
    
        private final cold;
    
        private Style1(boolean cold){
            this.cold = cold;
        }
    
        public boolean isCold(){
            return cold;
        }
    }
    

    Note that none of these 3 styles is likely ever to be a hotspot in your code. It is more important to write easier to read code and make adjustments for performance later as needed.