I have a small program testing literals behaviour in Java.
public static void main(String[] args) {
String a = "foo";
String b = "foo";
String c = new String("foo");
System.out.println(a == b);
System.out.println(a.intern() == b);
System.out.println(a.intern() == a);
System.out.println(a == c);
System.out.println(c == b);
}
The results are:
true
true
true
false
false
The bytecode (javap -c MyClass)
public MyClass();
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 foo
2: astore_1
3: ldc #2; //String foo
5: astore_2
6: new #3; //class java/lang/String
9: dup
10: ldc #2; //String foo
12: invokespecial #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: aload_2
21: if_acmpne 28
24: iconst_1
25: goto 29
28: iconst_0
29: invokevirtual #6; //Method java/io/PrintStream.println:(Z)V
32: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
35: aload_1
36: invokevirtual #7; //Method java/lang/String.intern:()Ljava/lang/String;
39: aload_2
40: if_acmpne 47
43: iconst_1
44: goto 48
47: iconst_0
48: invokevirtual #6; //Method java/io/PrintStream.println:(Z)V
51: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
54: aload_1
55: invokevirtual #7; //Method java/lang/String.intern:()Ljava/lang/String;
58: aload_1
59: if_acmpne 66
62: iconst_1
63: goto 67
66: iconst_0
67: invokevirtual #6; //Method java/io/PrintStream.println:(Z)V
70: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
73: aload_1
74: aload_3
75: if_acmpne 82
78: iconst_1
79: goto 83
82: iconst_0
83: invokevirtual #6; //Method java/io/PrintStream.println:(Z)V
86: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
89: aload_3
90: aload_2
91: if_acmpne 98
94: iconst_1
95: goto 99
98: iconst_0
99: invokevirtual #6; //Method java/io/PrintStream.println:(Z)V
102: return
}
Questions)
1) I see that system.out.println confirms, that a and b are the same objects
What are the difference between:
System.out.println(a == b);
System.out.println(a.intern() == b);
2) Is the String optimalization is working becaouse JVM puts the same String literals in NonHeap-PermanentGeneration-InternedStrings? Is that why InternedString were created? Then why even without a.intern() this is equal: a==b
What are the difference between: System.out.println(a == b); System.out.println(a.intern() == b);
Only that the second involves an entirely unnecessary method call.
Is the String optimalization is working becaouse JVM puts the same String literals in NonHeap-PermanentGeneration-InternedStrings? Is that why InternedString were created? Then why even without a.intern() this is equal: a==b
Because the compiler and JVM work together to automatically intern string literals. The compiler puts them in a section of the class file calls the "constant pool," and the JVM loads those constants into the intern pool when the class is loaded.
Just for clarity for casual readers: The correct way to compare strings in Java is to use the .equals method or similar, not ==. Using == with string instances is usually incorrect. The OP is presumably using it here in an effort to understand how and when strings are interned; in real code, you wouldn't. (Similarly: new String("foo") is almost always at best unnecessary, and more likely actively a bad idea. Again unless you're playing around with when and how strings are interned.)
Related
I'm trying to understand exactly how lambdas and higher order functions work at the JVM level in modern Java. I wrote this simple test class:
public final class Main {
public static void main(String[] args) {
var s = new Object[] { 1.0, 2.0, 3.0 };
System.out.println(sum(s, x -> 1000000.0));
}
public static double sum(Object[] s, Function<Object, Double> f) {
var r = 0.0;
for (var a : s) {
r += f.apply(a);
}
return r;
}
}
that compiles to this:
Compiled from "Main.java"
public final class prover.Main {
public prover.Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static double sum(java.lang.Object[], java.util.function.Function<java.lang.Object, java.lang.Double>);
Code:
0: dconst_0
1: dstore_2
2: aload_0
3: astore 4
5: aload 4
7: arraylength
8: istore 5
10: iconst_0
11: istore 6
13: iload 6
15: iload 5
17: if_icmpge 50
20: aload 4
22: iload 6
24: aaload
25: astore 7
27: dload_2
28: aload_1
29: aload 7
31: invokeinterface #7, 2 // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
36: checkcast #13 // class java/lang/Double
39: invokevirtual #15 // Method java/lang/Double.doubleValue:()D
42: dadd
43: dstore_2
44: iinc 6, 1
47: goto 13
50: dload_2
51: dreturn
public static void main(java.lang.String[]);
Code:
0: iconst_3
1: anewarray #2 // class java/lang/Object
4: dup
5: iconst_0
6: dconst_1
7: invokestatic #19 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
10: aastore
11: dup
12: iconst_1
13: ldc2_w #23 // double 2.0d
16: invokestatic #19 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
19: aastore
20: dup
21: iconst_2
22: ldc2_w #25 // double 3.0d
25: invokestatic #19 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
28: aastore
29: astore_1
30: getstatic #27 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: invokedynamic #33, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function;
39: invokestatic #36 // Method sum:([Ljava/lang/Object;Ljava/util/function/Function;)D
42: invokevirtual #42 // Method java/io/PrintStream.println:(D)V
45: return
private static java.lang.Double lambda$main$0(java.lang.Object);
Code:
0: ldc2_w #48 // double 1000000.0d
3: invokestatic #19 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
6: areturn
}
Now, the lambda function itself gets compiled to the private static method at the end, that much is clear enough. But where is it referred to? The code to call sum seems to be:
33: aload_1
34: invokedynamic #33, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function;
39: invokestatic #36 // Method sum:([Ljava/lang/Object;Ljava/util/function/Function;)D
Is that somehow referring to the lambda function? If so, how? What's the reference?
Using javap -p -v will produce a section labeled BootstrapMethods that lists all bootstrap methods used to initialize lambdas:
BootstrapMethods:
0: #41 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#42 (Ljava/lang/Object;)Ljava/lang/Object;
#43 REF_invokeStatic Scratch.lambda$main$0:(Ljava/lang/Object;)Ljava/lang/Double;
#44 (Ljava/lang/Object;)Ljava/lang/Double;
This one contains references to the methods implementing the actual code (Scratch.lambda$main$0 in my case, the exact name will vary depending on compiler-vendor/-version/-flags).
Note that the representation in the Class files has intentionally been kept at a fairly high level (there are bootstrap methods that return the actual code to be executed at run time). This means that the JVM doesn't have a lot of restrictions as to how to implement and optimize this. That also means that studying the bytecode will only ever tell you so much, because the JVM can pretty freely interpret what it sees there.
In Java, if I'm performing multiple methods on an object, I can chain them, or I can make a temporary variable, like so
Chaining
System.out.println( str.substring(0,4).substring(0,2));
Temp variable
String tmp = str.substring(0,4);
tmp = tmp.substring(0,2);
System.out.println(tmp);
Obviously, the difference is negligible in this example, but could make an impact when you're doing this over thousands of strings/some other object.
My question is, is one of these more "efficient" in terms of not making extra object allocations or filling the heap (and thus making GC get called sooner)?
I tried to compare the bytecodes of the two in a loop over a couple strings, but it looks similar, sans for the last few lines. I don't understand all the bytecode calls, so I'm not sure if any of these have to do with allocating new objects.
Compiled from "TestNoTmp.java"
public class TestNoTmp {
public TestNoTmp();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_4
1: anewarray #2 // class java/lang/String
4: dup
5: iconst_0
6: ldc #3 // String These
8: aastore
9: dup
10: iconst_1
11: ldc #4 // String Are__
13: aastore
14: dup
15: iconst_2
16: ldc #5 // String Some_
18: aastore
19: dup
20: iconst_3
21: ldc #6 // String Strings
23: aastore
24: astore_1
25: aload_1
26: astore_2
27: aload_2
28: arraylength
29: istore_3
30: iconst_0
31: istore 4
33: iload 4
35: iload_3
36: if_icmpge 69
39: aload_2
40: iload 4
42: aaload
43: astore 5
45: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
48: aload 5
50: iconst_0
51: iconst_4
52: invokevirtual #8 // Method java/lang/String.substring:(II)Ljava/lang/String;
55: iconst_0
56: iconst_2
57: invokevirtual #8 // Method java/lang/String.substring:(II)Ljava/lang/String;
60: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
63: iinc 4, 1
66: goto 33
69: return
}
public class TestTmp {
public TestTmp();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_4
1: anewarray #2 // class java/lang/String
4: dup
5: iconst_0
6: ldc #3 // String These
8: aastore
9: dup
10: iconst_1
11: ldc #4 // String Are__
13: aastore
14: dup
15: iconst_2
16: ldc #5 // String Some_
18: aastore
19: dup
20: iconst_3
21: ldc #6 // String Strings
23: aastore
24: astore_1
25: aload_1
26: astore_2
27: aload_2
28: arraylength
29: istore_3
30: iconst_0
31: istore 4
33: iload 4
35: iload_3
36: if_icmpge 77
39: aload_2
40: iload 4
42: aaload
43: astore 5
45: aload 5
47: iconst_0
48: iconst_4
49: invokevirtual #7 // Method java/lang/String.substring:(II)Ljava/lang/String;
52: astore 6
54: aload 6
56: iconst_0
57: iconst_2
58: invokevirtual #7 // Method java/lang/String.substring:(II)Ljava/lang/String;
61: astore 6
63: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
66: aload 6
68: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
71: iinc 4, 1
74: goto 33
77: return
}
In your example, you're working with Strings, which are immutable. In your code:
str.substring(0,4).substring(0,2)
the first call to substring must generate a new String object because str cannot be modified. Similarly, the second call to substring on that new String object will create another new String object.
The difference in bytecodes is simply a result of the order in which the compiler calls methods. In the TestTmp case, all the string manipulation occurs before the call to PrintStream. For TestNoTmp, the String calls happen within the PrintStream call, which is very logical when you look at the code.
To answer your question, this will make no difference in terms of object allocation and therefore GC impact.
I am working on my assignment in which I'm supposed to modify a class file using BCEL to print certain information when a getfield operation is called.
That includes printing a String if the value of referenced field is greater than 30.
The issue I'm having is creating the if condition.
Currently what I have is
instructionList.append(InstructionFactory.createDup(type.getSize()));
instructionList.append(new PUSH(constantPoolGen, 30));
instructionList.append(new ISUB());
instructionList.append(new IFGT(instructionList.append(instructionFactory.createPrintln(" !this value is greater than 30!"))));
First line duplicates the value that the getfield operation accesses.
The problem is the call to IFGT(). Upon execution I'm getting
Inconsistent stack height 2 != 3
I've tried printing the ISUB result and it is as expected, replacing the IFGT with a POP makes the program execute properly which leads me to belive that the last line is the issue.
Bytecode generated
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.io.IOException;
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3; //String Hello world
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: new #5; //class Test$myobject
11: dup
12: invokespecial #6; //Method Test$myobject."<init>":()V
15: astore_1
16: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
23: ldc #69; //String Before getfield:\n Test$myobject\n char\n ftok
25: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
28: getfield #7; //Field Test$myobject.ftok:C
31: ldc #71; //String
33: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
36: dup_x1
37: pop
38: invokevirtual #75; //Method java/io/PrintStream.print:(Ljava/lang/Object;)V
41: dup
42: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
45: dup_x1
46: pop
47: invokevirtual #77; //Method java/io/PrintStream.print:(C)V
50: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
53: ldc #79; //String
55: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
58: invokevirtual #8; //Method java/io/PrintStream.println:(C)V
61: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
64: aload_1
65: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
68: ldc #81; //String Before getfield:\n Test$myobject\n int\n pok
70: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
73: getfield #9; //Field Test$myobject.pok:I
76: ldc #71; //String
78: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
81: dup_x1
82: pop
83: invokevirtual #75; //Method java/io/PrintStream.print:(Ljava/lang/Object;)V
86: dup
87: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
90: dup_x1
91: pop
92: invokevirtual #83; //Method java/io/PrintStream.print:(I)V
95: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
98: ldc #79; //String
100: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
103: dup
104: bipush 30
106: isub
107: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
110: ldc #85; //String !this value is greater than 30!
112: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
115: ifgt 107
118: invokevirtual #10; //Method java/io/PrintStream.println:(I)V
121: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
124: aload_1
125: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
128: ldc #87; //String Before getfield:\n Test$myobject\n int\n pyk
130: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
133: getfield #11; //Field Test$myobject.pyk:I
136: ldc #71; //String
138: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
141: dup_x1
142: pop
143: invokevirtual #75; //Method java/io/PrintStream.print:(Ljava/lang/Object;)V
146: dup
147: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
150: dup_x1
151: pop
152: invokevirtual #83; //Method java/io/PrintStream.print:(I)V
155: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
158: ldc #79; //String
160: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
163: dup
164: bipush 30
166: isub
167: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
170: ldc #85; //String !this value is greater than 30!
172: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
175: ifgt 167
178: invokevirtual #10; //Method java/io/PrintStream.println:(I)V
181: iconst_5
182: invokestatic #12; //Method returnint:(I)I
185: pop
186: ldc #13; //String plok
188: invokestatic #14; //Method returnstring:(Ljava/lang/String;)Ljava/lang/String;
191: pop
192: invokestatic #15; //Method nothing:()V
195: return
public static int returnint(int);
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: iload_0
4: invokevirtual #10; //Method java/io/PrintStream.println:(I)V
7: iload_0
8: ireturn
public static java.lang.String returnstring(java.lang.String);
Code:
0: aload_0
1: areturn
public static void nothing();
Code:
0: return
}
I've tried googling for answers but unfortunatelly I haven't found anything.
I read Jon Skeet's answer about concatenating strings with +. I wonder whether the compiler also recognizes appending constand strings with a StringBuffer/StringBuilder.
This code for constructing a URL has a good intention:
StringBuffer sb = new StringBuffer(constant1);
sb.append(nonconstant);
sb.append("?");
sb.append(constant2);
sb.append("=");
sb.append(constant3);
sb.append("&");
sb.append(constant4);
sb.append("=");
sb.append(constant5);
However, if Stringbuffer.append() is not optimized by the compiler for constants, I'd say the following code would be more efficient:
StringBuffer sb = new StringBuffer(constant1);
sb.append(non-constant);
sb.append("?" + constant2 + "=" + constant3 + "&" + constant4 + "=" + constant5);
because the compiler would optimize the + string concatenation at compile time.
Why not try it out? In java 1.7, the main method of the following class:
public class Concat1
{
private static final String constant2 = "c2";
private static final String constant3 = "c3";
public void main(String[] args)
{
StringBuilder sb = new StringBuilder();
sb.append(args[0]);
sb.append("?");
sb.append(constant2);
sb.append("=");
sb.append(constant3);
System.out.println(sb.toString());
}
}
(I changed the number of constant for clarity) yields the following byte code:
public class Concat1 {
public Concat1();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."":()V
7: astore_2
8: aload_2
9: aload_1
10: iconst_0
11: aaload
12: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: pop
16: aload_2
17: ldc #5 // String ?
19: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: pop
23: aload_2
24: ldc #6 // String c2
26: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
29: pop
30: aload_2
31: ldc #7 // String =
33: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: aload_2
38: ldc #8 // String c3
40: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: pop
44: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
47: aload_2
48: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
51: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
54: return
}
while the following class:
public class Concat2
{
private static final String constant2 = "c2";
private static final String constant3 = "c3";
public void main(String[] args)
{
StringBuilder sb = new StringBuilder();
sb.append(args[0]);
sb.append("?" + constant2 + "=" + constant3);
System.out.println(sb.toString());
}
}
is compiled to:
public class Concat2 {
public Concat2();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."":()V
7: astore_2
8: aload_2
9: aload_1
10: iconst_0
11: aaload
12: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: pop
16: aload_2
17: ldc #5 // String ?c2=c3
19: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: pop
23: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
30: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
33: return
}
So apparently, you are right. In the second class the append method of the StringBuilder is only called twice, while in the first case it is called for each constant string.
Does the compiler optimize Stringbuffer/StringBuilder.append() for constant strings?
No.
However, I think that the premise of your proposed optimization is incorrect. I suggest that you take the two versions of the code and compile them. Then use javap to see what the compiled code looks like in each case.
(FWIW, I expect that your "optimization" will not improve the generated code. It will most likely create a second StringBuilder to concatenate the intermediate string, and convert that into a String. You end up with roughly the same number of append operations, plus the creation of an extra temporary StringBuilder and an extra temporary String.)
What are the pros and cons of using this:
String a = new String();
switch (i) {
case 1: a = "Cueck"; break;
case 2: a = "Blub"; break;
case 3: a = "Writing cases is BORING!"; break;
}
System.out.println(a);
Versus:
switch (i) {
case 1: System.out.println("Cueck"); break;
case 2: System.out.println("Blub"); break;
case 3: System.out.println("Writing cases is BORING!"); break;
}
Which generates better bytecode? And which generates more bytecode?
Your first option is neater and has less redundant code. One suggested change:
String a;
switch (i) {
case 1: a = "Cueck"; break;
case 2: a = "Blub"; break;
case 3: a = "Writing cases is BORING!"; break;
default: throw new IllegalStateException("Unknown option!");
}
System.out.println(a);
Don't create a String unnecessarily - a should be instatiated when required. A default case should either throw an exception or set a to a default value.
Which generates better bytecode? And which generates more bytecode?
I wouldn't worry about that. This doesn't strike me as a likely bottleneck in any real-life application. Also, you cannot be sure what the JVM will do to optimise the byte-code once your application is running.
Using javap -c classname you can check the bytecode yourself,
Here's option 1:
(Note, I had to initialise a = null otherwise it doesn't compile)
7: aconst_null
8: astore_2
9: iload_1
10: tableswitch{ //1 to 3
1: 36;
2: 42;
3: 48;
default: 51 }
36: ldc #3; //String Cueck
38: astore_2
39: goto 51
42: ldc #4; //String Blub
44: astore_2
45: goto 51
48: ldc #5; //String Writing cases is BORING!
50: astore_2
51: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream;
54: aload_2
55: invokevirtual #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
58: return
Here's option 2:
7: iload_1
8: tableswitch{ //1 to 3
1: 36;
2: 47;
3: 58;
default: 66 }
36: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
39: ldc #4; //String Cueck
41: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
44: goto 66
47: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
50: ldc #6; //String Blub
52: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
55: goto 66
58: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
61: ldc #7; //String Writing cases is BORING!
63: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
66: return
Personally, I don't think there's better bytecode in this instance, I find option 1 more readable.
I don't think there will be much difference in bytecode size but I suggest first approach. If you in some future code changes decide not to call System.out.println(a) but logger.debug(a) you will change that only on one place and not on all case switches.