Why can't I have a duplicate case in my switch statement? - java

I understand that this will fail to compile:
int caseNum = 2;
switch(caseNum)
{
case 2:
System.out.println("Happy");
break;
case 2:
System.out.println("Birthday");
break;
case 2:
System.out.println("To the ground!");
break;
default:
System.out.println("<3");
break;
}
I know that the case statements are conflicting and that the compiler "doesn't know which 'case 2' I am talking about". A few peers of mine and myself were wondering behind the scenes what the conflict is and had heard that switch statements are converted into hash-maps. Is that the case, does a switch statement become a hash-map during compile time and the conflict in the mapping create the error?
So far I have looked around Stack Overflow and Google for an answer, and the information must be out there already, but I am unsure how to express the question correctly it would appear. Thanks in advance!

A hash map is just one way that a switch statement could be compiled, but in any case, you can imagine having duplicate cases as trying to have multiple values for the same key in a regular HashMap. The compiler doesn't know which one of the values corresponds to the key, and so emits an error.
switch statements could also be compiled into a jump table, in which case this is still ambiguous for a very similar reason -- you have multiple different possibilities for the same jump location.
switch statements could also be compiled into a binary search, in which case you still have the same problem -- multiple different results for the same key being searched for.
Just in case you were curious, I did a small test case to see what javac would compile the switch to. From this (slightly modified) source:
public static void main(final String[] args) {
final int caseNum = 2;
switch (caseNum) {
case 1:
System.out.println("Happy");
break;
case 2:
System.out.println("Birthday");
break;
case 3:
System.out.println("To the ground!");
break;
default:
System.out.println("<3");
break;
}
}
You get this bytecode:
public static void main(java.lang.String[]);
Code:
0: iconst_2
1: tableswitch { // 1 to 3
1: 28
2: 39
3: 50
default: 61
}
28: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
31: ldc #3 // String Happy
33: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
36: goto 69
39: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
42: ldc #5 // String Birthday
44: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
47: goto 69
50: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
53: ldc #6 // String To the ground!
55: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
58: goto 69
61: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
64: ldc #7 // String <3
66: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
69: return
So at least for this small switch, a jump table seems to be used. I have heard that the way switches are compiled depends partly on their size, but I don't know the exact point at which the implementation changes. 20 cases still seem to be implemented as a jump table...
Turns out String switch statements are implemented a bit differently. From this source:
public static void main(final String[] args) {
final String caseNum = "2";
switch (caseNum) {
case "1":
System.out.println("Happy");
break;
case "2":
System.out.println("Birthday");
break;
case "3":
System.out.println("To the ground!");
break;
default:
System.out.println("<3");
break;
}
}
You get this bytecode:
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String 2
2: astore_2
3: iconst_m1
4: istore_3
5: aload_2
6: invokevirtual #3 // Method java/lang/String.hashCode:()I
9: tableswitch { // 49 to 51
49: 36
50: 50
51: 64
default: 75
}
36: aload_2
37: ldc #4 // String 1
39: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
42: ifeq 75
45: iconst_0
46: istore_3
47: goto 75
50: aload_2
51: ldc #2 // String 2
53: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
56: ifeq 75
59: iconst_1
60: istore_3
61: goto 75
64: aload_2
65: ldc #6 // String 3
67: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
70: ifeq 75
73: iconst_2
74: istore_3
75: iload_3
76: tableswitch { // 0 to 2
0: 104
1: 115
2: 126
default: 137
}
104: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
107: ldc #8 // String Happy
109: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
112: goto 145
115: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
118: ldc #10 // String Birthday
120: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
123: goto 145
126: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
129: ldc #11 // String To the ground!
131: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
134: goto 145
137: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
140: ldc #12 // String <3
142: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
145: return
So you still have jump tables, but you have two. Bit of a roundabout process -- take the hash code, switch on that, based on the case load another constant, and from that do a "normal" switch (I think). But same basic process, I suppose?

Historically, switch statements were/could be implemented as jump tables (i.e. a mapping of value to destination address). Depending on the implementation of the table, it may be impossible to have two entries for the same value pointing to different addresses. Even if it were possible, how would you handle that duplicate value? Return from the first handler and then go to second handler? Never execute the second handler?
It makes no sense to allow this.

Related

How does final keyword work with string immutability? [duplicate]

This question already has an answer here:
How is concatenation of final strings done in Java?
(1 answer)
Closed 2 years ago.
I have the following code. I understand the concept of java string immutability and string constant pool. I don't understand why 'name1 == name2' results false and 'name2 == name3' results true in the following program. How are the string variables name1, name2, and name3 placed in the string constant pool?
public class Test {
public static void main(String[] args) {
final String firstName = "John";
String lastName = "Smith";
String name1 = firstName + lastName;
String name2 = firstName + "Smith";
String name3 = "John" + "Smith";
System.out.println(name1 == name2);
System.out.println(name2 == name3);
}
}
Output:
false
true
The answer is fairly simple: As a shortcut, java treats certain concepts as a so-called 'compile time constant' (CTC). The idea is to entirely inline a variable, at the compilation level (which is extraordinary; normally javac basically just bashes your java source file into a class file using very simple and easily understood transformations, and the fancypants optimizations occur at runtime during hotspot).
For example, if you do this:
Save to UserOfBatch.java:
class BatchOConstants {
public static final int HELLO = 5;
}
public class UserOfBatch {
public static void main(String[] args) {
System.out.println(BatchOConstants.HELLO);
}
}
Run on the command line:
> javac UserOfBatch.java
> java UserOfBatch
5
> javap -c UserOfBatch # javap prints bytecode
public static void main(java.lang.String[]);
Code:
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: iconst_5
4: invokevirtual #15 // Method java/io/PrintStream.println:(I)V
7: return
Check out line 3 up there. iconst_5. That 5? It was hardcoded!!
No reference to BatchOConstants remains. Let's test that:
on command line:
> rm BatchOConstants.class
> java UserOfBatch
5
Wow. The code ran even though it's missing the very class file that should be providing that 5, thus proving, it was 'hardcoded' by the compiler itself and not the runtime.
Another way to 'observe' CTC-ness of any value is annoparams. An annotation parameter must be hardcoded into the class file by javac, so you can't pass expressions. Given:
public #interface Foo {
long value();
I can't write: #Foo(System.currentTimeMillis()), because System.cTM obviously isn't a compile time constant. But I can write #Foo(SomeClass.SOME_STATIC_FINAL_LONG_FIELD) assuming that the value assigned to S_S_F_L_F is a compile time constant. If it's not, that #Foo(...) code would not compile. If it is, it will compile: CTC-ness now determines whether your code compiles or not.
There are specific rules about when the compiler is allowed to construe something as a 'compile time constant' and go on an inlining spree. For example, null is not an inline constant, ever. Oversimplifying, but:
For fields, the field must be static and final and have a primitive or String type, initialized on the spot (in the same breath, not later in a static block), with a constant expression, that isn't null.
For local variables, the rules are very similar, except, the need for them to be static is obviously waived as they cannot be. Other than that, all the fixins apply: final, primitive-or-String, non-null, initialized on the spot, and with a constant expression.
Unfortunately, CTC-ness of a local is harder to directly observe. The code you wrote is a fine way to indirectly observe it though. You've proven with your prints that firstName is CTC and lastName is not.
We can observe a select few things though. So let's take your code, compile it, and toss it at javap to witness the results. Via Jonas Konrad's online javap tool, let's analyse:
public Main() {
final String a = "hello";
String b = "world";
String c = a + "!";
String d = b + "!";
System.out.println(c == "hello!");
System.out.println(d == "world!");
}
The relevant parts:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: ldc #7 // String hello
6: astore_1
start local 1 // java.lang.String a
7: ldc #9 // String world
9: astore_2
start local 2 // java.lang.String b
10: ldc #11 // String hello!
12: astore_3
start local 3 // java.lang.String c
13: aload_2
14: invokedynamic #13, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
19: astore 4
start local 4 // java.lang.String d
Note how 'start local 2' (which is c; javap starts counting at 0) shows just loading hello! as a complete constant, but 'start local 3' (which is d) shows loading 2 constants and invoking makeConcat to tie em together.
run javap -c Test after you compiled the code,
you will see that
Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String Smith
2: astore_2
3: aload_2
4: invokedynamic #3, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
9: astore_3
10: ldc #4 // String JohnSmith
12: astore 4
14: ldc #4 // String JohnSmith
16: astore 5
18: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
21: aload_3
22: aload 4
24: if_acmpne 31
27: iconst_1
28: goto 32
31: iconst_0
32: invokevirtual #6 // Method java/io/PrintStream.println:(Z)V
35: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
38: aload 4
40: aload 5
42: if_acmpne 49
45: iconst_1
46: goto 50
49: iconst_0
50: invokevirtual #6 // Method java/io/PrintStream.println:(Z)V
53: return
}
As you can see
4: invokedynamic #3, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
in public static void main(java.lang.String[]);, this is the actual compiled byte code when running firstname + lastname. So generated String will not have the same hashcode as "JohnSmith" in constant pool.
where as on
10: ldc #4 // String JohnSmith
and
14: ldc #4 // String JohnSmith
this is the byte code generated by the compiler when doing both
firstname + "Smith" and "John" + "Smith" which means both actually reading from the constant pool.
This is the reason why when you comparing name1 with name2 using == it will return false. since name2 and name3 reference the same string from the constant pool. Hench it return true when compare with ==.
This is the reason why it is not a good idea to compare 2 string with ==. Please use String.equals() when doing String comparison.
since both
Let's look at the bytecode with final:
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #7 // String Smith
2: astore_1
3: aload_1
4: invokedynamic #9, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
9: astore_2
10: ldc #13 // String JohnSmith
12: astore_3
13: ldc #13 // String JohnSmith
15: astore 4
17: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
20: aload_2
21: aload_3
22: if_acmpne 29
25: iconst_1
26: goto 30
29: iconst_0
30: invokevirtual #21 // Method java/io/PrintStream.println:(Z)V
33: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
36: aload_3
37: aload 4
39: if_acmpne 46
42: iconst_1
43: goto 47
46: iconst_0
47: invokevirtual #21 // Method java/io/PrintStream.println:(Z)V
50: return
}
And the bytecode without final:
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #7 // String John
2: astore_1
3: ldc #9 // String Smith
5: astore_2
6: aload_1
7: aload_2
8: invokedynamic #11, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
13: astore_3
14: aload_1
15: invokedynamic #15, 0 // InvokeDynamic #1:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
20: astore 4
22: ldc #18 // String JohnSmith
24: astore 5
26: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
29: aload_3
30: aload 4
32: if_acmpne 39
35: iconst_1
36: goto 40
39: iconst_0
40: invokevirtual #26 // Method java/io/PrintStream.println:(Z)V
43: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
46: aload 4
48: aload 5
50: if_acmpne 57
53: iconst_1
54: goto 58
57: iconst_0
58: invokevirtual #26 // Method java/io/PrintStream.println:(Z)V
61: return
}
As you can see, with final, Java recognizes both the left and right hand sides of + to be constant, so it replaces the concatenation with the constant string "JohnSmith" at compile time. The only call to makeConcatWithConstants (to concatenate strings) is made for firstName + lastName, since lastName isn't final.
In the second example, there are two calls to makeConcatWithConstants, one for firstName + lastName and another for firstName + "Smith", since Java doesn't recognize firstName as a constant.
That's why name1 == name2 is false in your example: name2 is a constant "JohnSmith" in the string pool, whereas name1 is dynamically computed at runtime. However, name2 and name3 are both constants in the string pool, which is why name2 == name3 is true.

Method should return boolean, returns int

I have to create a method similiar to one that's inside a JAR. I have no code, so I disassabled to study.
I used JD-GUI, which tells me, is:
private static boolean checkMe(Date paramDate, String paramString)
throws REUException {
int i = 1;
int j, k;
// unrelated stuff
if (j > k)
i = 0;
return i;
}
You can see in method signature it should return a boolean, but it really returns an ìnt, which is not allowed in Java
So, I thought there were something wrong with JD-GUI.
I tried to dissasemble using javap, but I still don't get a clue:
Using: javap -c -s -verbose -private Class
I get:
private static boolean checkMe(java.util.Date, java.lang.String) throws reu.exceptions.REUException;
Signature: (Ljava/util/Date;Ljava/lang/String;)Z
Code:
Stack=4, Locals=7, Args_size=2
0: iconst_1
1: istore_2
2: getstatic #34; //Field iniciado:Z
5: ifne 44
8: ldc_w #35; //class reu/modulos/STDWDATES
11: dup
12: astore_3
13: monitorenter
14: getstatic #34; //Field iniciado:Z
17: ifne 32
20: new #35; //class reu/modulos/STDWDATES
23: dup
24: invokespecial #36; //Method "<init>":()V
27: pop
28: iconst_1
29: putstatic #34; //Field iniciado:Z
32: aload_3
33: monitorexit
34: goto 44
37: astore 4
39: aload_3
40: monitorexit
41: aload 4
43: athrow
44: aconst_null
45: getstatic #37; //Field AlmacenFechaCal:Ljava/util/HashMap;
48: aload_1
49: invokevirtual #38; //Method java/util/HashMap.get:(Ljava/lang/Object;)Ljava/lang/Object;
52: if_acmpne 67
55: new #39; //class reu/exceptions/REUException
58: dup
59: bipush 58
61: bipush 17
63: invokespecial #40; //Method reu/exceptions/REUException."<init>":(II)V
66: athrow
67: getstatic #37; //Field AlmacenFechaCal:Ljava/util/HashMap;
70: aload_1
71: invokevirtual #38; //Method java/util/HashMap.get:(Ljava/lang/Object;)Ljava/lang/Object;
74: checkcast #41; //class reu/modulos/AlmancenFechas
77: astore_3
78: aload_3
79: invokevirtual #42; //Method reu/modulos/AlmancenFechas.getFechaIni:()I
82: istore 4
84: invokestatic #43; //Method java/util/Calendar.getInstance:()Ljava/util/Calendar;
87: astore 5
89: aload 5
91: aload_0
92: invokevirtual #44; //Method java/util/Calendar.setTime:(Ljava/util/Date;)V
95: aload 5
97: iconst_1
98: invokevirtual #45; //Method java/util/Calendar.get:(I)I
101: istore 6
103: iload 4
105: iload 6
107: if_icmple 112
110: iconst_0
111: istore_2
112: iload_2
113: ireturn
Exception table:
from to target type
14 34 37 any
37 41 37 any
Exceptions:
throws reu.exceptions.REUException
I guess clue is ireturn expression in 113. According to oracle documentation for ireturn, it returns an int.
In this Casting conversions to primitive types, looks like a conversion from int to boolean, unlike C/C++, is not allowed.
How is that possible? Is there an implicit cast?
Thanks.
The JVM uses integers to represent booleans. From the JVM Specification §2.3.4:
2.3.4. The boolean Type
Although the Java Virtual Machine defines a boolean type, it only provides very limited support for it. There are no Java Virtual Machine instructions solely dedicated to operations on boolean values. Instead, expressions in the Java programming language that operate on boolean values are compiled to use values of the Java Virtual Machine int data type.
You can check this out your self:
$ cat Test.java
class Test {
boolean m() {
return true; <------------
}
}
$ javac Test.java
$ javap -c Test
Compiled from "Test.java"
class Test {
[...]
boolean m();
Code:
0: iconst_1
1: ireturn <------------
}
The JVM represents booleans as ints: true is represented by 1 and false by 0. That's why the compiled code uses integers.
In other words, if you write that method and use booleans as follows (which probably what the original source was):
private static boolean checkMe(Date paramDate, String paramString)
throws REUException {
boolean i = true;
int j, k;
// unrelated stuff
if (j > k)
i = false;
return i;
}
then the generated bytecode would contain ireturn at the end.

Can a java compiler optimize this code away?

Is a java compiler or runtime ( or any other language compiler ) smart enough to realize branch 3 can never happen , and optimize it away? I've seen this kind of "defensive programming" with many beginning developers, and wonder if this dead weight stays in the bytecode.
import java.util.Random;
class Example
{
public static void main(String[] args) {
int x = new Random().nextInt() % 10;
if ( x < 5 )
{
System.out.println("Case 1");
}
else
if ( x >= 5 )
{
System.out.println("Case 2");
}
else
{
System.out.println("Case 3");
}
}
}
or even this more blunt case
boolean bool = new Random().nextBoolean();
if ( bool )
{
System.out.println("Case 1");
}
else
if ( bool )
{
System.out.println("Case 2");
}
The Java 8 compiler I have doesn't seem to optimize it away. Using "javap -c" to examine the byte code after compiling:
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/util/Random
3: dup
4: invokespecial #3 // Method java/util/Random."<init>":()V
7: invokevirtual #4 // Method java/util/Random.nextInt:()I
10: bipush 10
12: irem
13: istore_1
14: iload_1
15: iconst_5
16: if_icmpge 30
19: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
22: ldc #6 // String Case 1
24: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: goto 54
30: iload_1
31: iconst_5
32: if_icmplt 46
35: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
38: ldc #8 // String Case 2
40: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
43: goto 54
46: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
49: ldc #9 // String Case 3
51: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
54: return
}
The string "Case 3" still exists in the byte code.

Why is the switch statement faster than if else for String in Java 7?

In Java 7 a string object can be in the expression of a switch statement. Can someone explain the below statement from official documentation?
The Java compiler generates generally more efficient bytecode from switch statements that use String objects than from chained if-then-else statements.
Java Code
Having two versions of a class, e.g.
With if-then-else:
public class IfThenElseClass {
public static void main(String[] args) {
String str = "C";
if ("A".equals(str)) {
} else if ("B".equals(str)) {
} else if ("C".equals(str)) {
}
}
}
With switch:
public class SwitchClass {
public static void main(String[] args) {
String str = "C";
switch (str) {
case "A":
break;
case "B":
break;
case "C":
break;
}
}
}
Bytecode
Let's take a look at the bytecode. Getting the bytecode for if-then-else version:
Compiled from "CompileSwitch.java"
public class CompileSwitch {
public CompileSwitch();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #16 // String C
2: astore_1
3: ldc #18 // String A
5: aload_1
6: invokevirtual #20 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
9: ifne 28
12: ldc #26 // String B
14: aload_1
15: invokevirtual #20 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
18: ifne 28
21: ldc #16 // String C
23: aload_1
24: invokevirtual #20 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
27: pop
28: return
}
Getting the bytecode for switch version:
Compiled from "CompileSwitch.java"
public class CompileSwitch {
public CompileSwitch();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #16 // String C
2: astore_1
3: aload_1
4: dup
5: astore_2
6: invokevirtual #18 // Method java/lang/String.hashCode:()I
9: lookupswitch { // 3
65: 44
66: 56
67: 68
default: 77
}
44: aload_2
45: ldc #24 // String A
47: invokevirtual #26 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
50: ifne 77
53: goto 77
56: aload_2
57: ldc #30 // String B
59: invokevirtual #26 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
62: ifne 77
65: goto 77
68: aload_2
69: ldc #16 // String C
71: invokevirtual #26 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
74: ifne 77
77: return
}
Conclusion
In the first version compares the string by calling the equals method for each condition, until it is found.
In the second version is obtained first hashCode of the string. Then this is compared with the values ​​hashCode each case. See the lookupswitch. If any of these values ​​is repeated just happens to run the code for the case. Otherwise, call the equals method of the cases tied. This is much faster than ever call the equals method only.
switch on strings can be faster for the same reason why a lookup in a hash set of strings may be faster than a lookup in a list of strings: you can do a lookup in O(1) rather than in O(N), where N is the number of strings.
Recall that switch is more efficient than a chain of if-then-else statements because it is a calculated jump: an offset in code is calculated based on the value, and then the jump to that offset is executed. Java can pull a similar trick on strings using the mechanism similar to that employed in hash maps and hash sets.
It's more efficient something like:
switch(yourString) {
case "text1":
// your code
break;
case "text2":
// etc.
}
than the correspondent:
if (yourString.equals("text1")) {
// your code
} else if (yourString.equals("text2")) {
// etc.
}
i guess what it means, or what i understand is that the bytecode (when you compile your java class) that created from an switch statement using string is faster and more efficient than the bytecode that is created from an if-else statement using string. both can do the same job, bit switch is apparently more efficient.
switch (str) {
case "A":
// do something
break;
case "B":
// do something
break;
default:
//do something
break;
}
is better than
if(str.equals("A")) {
//do something
} else if(str-equals("B")) {
//do something
} else {
//do something
}

A Switch Java problem : case expressions must be constant expressions

I having a problem in my switch/case statement. The error says : "Case expressions must be constant expressions". I understand the error and I can resolve it using If but can someone tells me why the case expression must be constant in a switch/case.
A code example of my error :
public boolean onOptionsItemSelected(MenuItem item) {
int idDirectory = ((MenuItem) findViewById(R.id.createDirectory)).getItemId();
int idSuppression = ((MenuItem) findViewById(R.id.recycleTrash)).getItemId();
int idSeeTrash = ((MenuItem) findViewById(R.id.seeTrash)).getItemId();
switch (item.getItemId()) {
case idDirectory:
createDirectory(currentDirectory);
break;
case idSuppression:
recycleTrash();
break;
case idSeeTrash:
seeTrash();
break;
}
return super.onOptionsItemSelected(item);
}
Thx for your explanation !!
So it can be evaluated during the compilation phase ( statically check )
See: http://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.11 for a formal definition of the switch.
Additionally it may help you to understand better how that switch is transformed into bytecode:
class Switch {
void x(int n ) {
switch( n ) {
case 1: System.out.println("one"); break;
case 9: System.out.println("nine"); break;
default: System.out.println("nothing"); break;
}
}
}
And after compiling:
C:\>javap -c Switch
Compiled from "Switch.java"
class Switch extends java.lang.Object{
Switch();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
void x(int);
Code:
0: iload_1
1: lookupswitch{ //2
1: 28;
9: 39;
default: 50 }
28: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
31: ldc #3; //String one
33: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
36: goto 58
39: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
42: ldc #5; //String nine
44: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
47: goto 58
50: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
53: ldc #6; //String nothing
55: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
58: return
}
See that line marked as 1:
1: lookupswitch{ //2
1: 28;
9: 39;
default: 50 }
It evaluates the value and goes to some other line. For instance if value is 9 it will jump to instruction 39:
39: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
42: ldc #5; //String nine
44: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
47: goto 58
Which in turn jumps to instruction 58 :
58: return
All this wouldn't be possible if it was evaluated dynamically. That's why.
in eclipse IDE is simple ,in switch sentence CTRL + 1 and convert switch sentence - if-else sentence http://tools.android.com/tips/non-constant-fields
The idDirectory and others need to be a constant and not a declared variable. Switch will not work in this case, you need switch to if-else construct.
EDIT I see what OP meant. That is just how switch works in java language.

Categories