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.
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.