Saturday, November 7, 2009

PowerMock - Mocking Static Methods

Déjà vu!

I feel a bit uneasy when writing unit test against codes that invoke "heavyweight" static methods. The static methods that I have seen are unusually full of testosterone, hairy and they do stuffs that are quite unimaginable such as reflection, acquiring network resources, class loading and ahem... Do we still need to go further?

As I sit here and start recalling the static methods I last looked at, indeed, life is BLEAK.

At times, I really wish that I could close my eyes and quietly sweep them under the carpet - "Look Ma, I don't have to worry about static methods anymore!"


Wishful thinking.

One way to get around this problem is to provide a helper class with non-static wrapper methods. I personally wouldn't want to go down this path as it adds an extra class and methods, not to mention complexity, to the codebase just to delegate calls to static methods without introducing any real benefit. Another way is to partial mock or...


PowerMock to the rescue!

After searching high and low for the best way to solve this by mocking, my friend Google suggested that I look at PowerMock.

To see PowerMock in action, let's write a sample class with static method:
SimonGarfunkel.java

1:  package test1;  
2: public class SimonGarfunkel {
3: public static void simonSaysGarfunkel() {
4: System.out.println("simon says garfunkel");
5: }
6: }


And the test class:
SimonGarfunkelTest.java

1:  package test1;
2: import org.junit.Test;
3: import org.junit.runner.RunWith;
4: import org.powermock.api.easymock.PowerMock;
5: import org.powermock.core.classloader.annotations.PrepareForTest;
6: import org.powermock.modules.junit4.PowerMockRunner;
7: @RunWith(PowerMockRunner.class)
8: @PrepareForTest(SimonGarfunkel.class)
9: public class SimonGarfunkelTest {
10: @Test
11: public void simonSaysGarfunkel() {
12: System.out.println("simonSaysGarfunkel");
13: SimonGarfunkel.simonSaysGarfunkel();
14: }
15: @Test
16: public void mockSimonSaysGarfunkel() {
17: System.out.println("mockSimonSaysGarfunkel");
18: PowerMock.mockStatic(SimonGarfunkel.class);
19: SimonGarfunkel.simonSaysGarfunkel();
20: PowerMock.expectLastCall().atLeastOnce();
21: PowerMock.replay(SimonGarfunkel.class);
22: SimonGarfunkel.simonSaysGarfunkel();
23: PowerMock.verify(SimonGarfunkel.class);
24: }
25: }


The code at line 20 and 23 ensures that "SimonGarfunkel.simonSaysGarfunkel()" is invoked at least once.

Also include the following dependencies into the pom file:
pom.xml

   <repositories> 
<repository>
<id>powermock-repo</id>
<url>http://powermock.googlecode.com/svn/repo/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-release-full</artifactId>
<version>1.3.1</version>
<scope>test</scope>
</dependency>
</dependencies>


For some reason, Maven2 didn't quite resolve powermock artifact even after I added the powermock repository. I had to manually download and install this into my local repo.

Friday, October 2, 2009

Integers and Equality Operator

On a lucky day, you could get away by comparing Integers using the equality operator:

1:  public class IntTest {  
2: public static void main(String[] args) {
3: Integer a = 100;
4: Integer b = 100;
5: System.out.println(a == b);
6: }
7: }


Running IntTest.class results to:

 true  


But you may not find yourself lucky if you have a code similar to the one found below:

1:  public class IntTest2 {  
2: public static void main(String[] args) {
3: Integer a = 200;
4: Integer b = 200;
5: System.out.println(a == b);
6: }
7: }


Running IntTest2.class results to:

 false  



Let's write another equality test to check our sanity:

1:  public class IntBox {  
2: public static void main(String[] args) {
3: Integer a1 = 127;
4: Integer a2 = 127;
5: Integer b1 = -128;
6: Integer b2 = -128;
7: Integer c1 = 128;
8: Integer c2 = 128;
9: Integer d1 = -129;
10: Integer d2 = -129;
11: System.out.println("a1 == a1 " + (a1 == a2));
12: System.out.println("b1 == b2 " + (b1 == b2));
13: System.out.println("c1 == c2 " + (c1 == c2));
14: System.out.println("d1 == d2 " + (d1 == d2));
15: }
16: }


Running the code gives us:

 a1 == a1 true  
b1 == b2 true
c1 == c2 false
d1 == d2 false


Yes, comparing 127 and -128 results to true but not 128 and -129.

Let's disassemble IntBox.class to investigate further by running "javap -c IntBox":

1:  Compiled from "IntBox.java"  
2: public class IntBox extends java.lang.Object{
3: public IntBox();
4: Code:
5: 0: aload_0
6: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
7: 4: return
8: public static void main(java.lang.String[]);
9: Code:
10: 0: bipush 127
11: 2: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
12: 5: astore_1
13: 6: bipush 127
14: 8: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
15: 11: astore_2
16: 12: bipush -128
17: 14: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
18: 17: astore_3
19: 18: bipush -128
20: 20: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
21: 23: astore 4
22: 25: sipush 128
23: 28: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
24: 31: astore 5
25: 33: sipush 128
26: 36: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
27: 39: astore 6
28: 41: sipush -129
29: 44: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
30: 47: astore 7
31: 49: sipush -129
32: 52: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
33: 55: astore 8
34: 57: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
35: 60: new #4; //class java/lang/StringBuilder
36: 63: dup
37: 64: invokespecial #5; //Method java/lang/StringBuilder."<init>":()V
38: 67: ldc #6; //String a1 == a1
39: 69: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
40: 72: aload_1
41: 73: aload_2
42: 74: if_acmpne 81
43: 77: iconst_1
44: 78: goto 82
45: 81: iconst_0
46: 82: invokevirtual #8; //Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
47: 85: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
48: 88: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
49: 91: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
50: 94: new #4; //class java/lang/StringBuilder
51: 97: dup
52: 98: invokespecial #5; //Method java/lang/StringBuilder."<init>":()V
53: 101: ldc #11; //String b1 == b2
54: 103: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
55: 106: aload_3
56: 107: aload 4
57: 109: if_acmpne 116
58: 112: iconst_1
59: 113: goto 117
60: 116: iconst_0
61: 117: invokevirtual #8; //Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
62: 120: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
63: 123: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
64: 126: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
65: 129: new #4; //class java/lang/StringBuilder
66: 132: dup
67: 133: invokespecial #5; //Method java/lang/StringBuilder."<init>":()V
68: 136: ldc #12; //String c1 == c2
69: 138: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
70: 141: aload 5
71: 143: aload 6
72: 145: if_acmpne 152
73: 148: iconst_1
74: 149: goto 153
75: 152: iconst_0
76: 153: invokevirtual #8; //Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
77: 156: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
78: 159: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
79: 162: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
80: 165: new #4; //class java/lang/StringBuilder
81: 168: dup
82: 169: invokespecial #5; //Method java/lang/StringBuilder."<init>":()V
83: 172: ldc #13; //String d1 == d2
84: 174: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
85: 177: aload 7
86: 179: aload 8
87: 181: if_acmpne 188
88: 184: iconst_1
89: 185: goto 189
90: 188: iconst_0
91: 189: invokevirtual #8; //Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
92: 192: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
93: 195: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
94: 198: return
95: }



From line 10 to 33, the compiler converts our integer assignments to calling Integer.valueOf(int).

Looking at Integer.java source, the Integer class performs special cache operation whenever valueOf(int i) method is called:

(From JDK6):
1:  public static Integer valueOf(int i) {  
2: if(i >= -128 && i <= IntegerCache.high)
3: return IntegerCache.cache[i + 128];
4: else
5: return new Integer(i);
6: }


and for private static class IntegerCache found in Integer.java (neglect line 8 to 15 to keep things simple for now):

1:  private static class IntegerCache {  
2: static final int high;
3: static final Integer cache[];
4: static {
5: final int low = -128;
6: // high value may be configured by property
7: int h = 127;
8: if (integerCacheHighPropValue != null) {
9: // Use Long.decode here to avoid invoking methods that
10: // require Integer's autoboxing cache to be initialized
11: int i = Long.decode(integerCacheHighPropValue).intValue();
12: i = Math.max(i, 127);
13: // Maximum array size is Integer.MAX_VALUE
14: h = Math.min(i, Integer.MAX_VALUE - -low);
15: }
16: high = h;
17: cache = new Integer[(high - low) + 1];
18: int j = low;
19: for(int k = 0; k < cache.length; k++)
20: cache[k] = new Integer(j++);
21: }
22: private IntegerCache() {}
23: }


* Line 17 initialises the cache array to 256 in size, assuming 127 is the highest value
* Line 19 to 21 create the integer objects from -128 to 127 and stores them into "cache" variable

From the statements above, we see that Integers are cached starting from -128 to 127 and that suggests why a1/a2 and b1/b2 equality operator comparison works but not for c1/c2 and d1/d2.

Sunday, September 27, 2009

Deployment Plan Patch Contribution for Clownfish

I admit I've been out of action since the last release of Clownfish v0.1.1-SNAPSHOT, not to mention my original Clownfish blog had been removed by Blogger.com for unknown reason - can't be bothered to chase this up. :D

During my absence, I've spent a little bit of time looking at Android+ZXing early this year and MyFaces Orchestra recently. (Still trying to get my hands dirty with Google App Engine - ported Hibernate Annotations to JPA, still have a long way to go.)



Yesterday, I went back to have a look at my plugin, with much surprise a "Good Samaritan" offered a patch code that would enable Clownfish to deploy through a deployment plan, unit tests included! This is one of the functionality that I left off that I find I hardly use. I'm glad that someone had tried using Clownfish as a Maven2 Glassfish deployer!

Looking forward this coming long weekend to review and hopefully merge this patch back to Clownfish codebase.

Burned by Public Constants

We define two classes, one class exposes a public constant while the other class consumes the constant value.

Const.java:
1:  public class Const {  
2: public static final int VALUE = 600;
3: public void test() {
4: int a = VALUE;
5: System.out.println("a=" + a);
6: }
7: public static void main(String[] args) {
8: new Const().test();
9: }
10: }


ConstClient.java:
1:  public class ConstClient {  
2: public void test() {
3: int b = Const.VALUE;
4: System.out.println("b=" + b);
5: }
6: public static void main(String[] args) {
7: new ConstClient().test();
8: }
9: }


Compile and run both classes:
Const.java displays:
 a=600  


ConstClient.java displays:
 b=600  


So far so good, this is what we expected.

Let's try disassembling them with "javap -c Const"
1:  Compiled from "Const.java"  
2: public class Const extends java.lang.Object{
3: public static final int VALUE;
4: public Const();
5: Code:
6: 0: aload_0
7: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
8: 4: return
9: public void test();
10: Code:
11: 0: sipush 600
12: 3: istore_1
13: 4: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
14: 7: new #3; //class java/lang/StringBuilder
15: 10: dup
16: 11: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V
17: 14: ldc #5; //String a=
18: 16: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: 19: iload_1
20: 20: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
21: 23: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: 26: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: 29: return
24: public static void main(java.lang.String[]);
25: Code:
26: 0: new #10; //class Const
27: 3: dup
28: 4: invokespecial #11; //Method "<init>":()V
29: 7: invokevirtual #12; //Method test:()V
30: 10: return
31: }


and "javap -c ConstClient"
1:  Compiled from "ConstClient.java"  
2: public class ConstClient extends java.lang.Object{
3: public ConstClient();
4: Code:
5: 0: aload_0
6: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
7: 4: return
8: public void test();
9: Code:
10: 0: sipush 600
11: 3: istore_1
12: 4: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
13: 7: new #3; //class java/lang/StringBuilder
14: 10: dup
15: 11: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V
16: 14: ldc #5; //String b=
17: 16: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: 19: iload_1
19: 20: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
20: 23: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
21: 26: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
22: 29: return
23: public static void main(java.lang.String[]);
24: Code:
25: 0: new #10; //class ConstClient
26: 3: dup
27: 4: invokespecial #11; //Method "<init>":()V
28: 7: invokevirtual #12; //Method test:()V
29: 10: return
30: }


We see that from Const.class line 11 and ConstClient.class line 10, the constant value 600 is pushed onto the stack memory at slot #1.

Let's try changing Const.java constant value from 600 to 500.
1:  public class Const {  
2: public static final int VALUE = 500;
3: public void test() {
4: int a = VALUE;
5: System.out.println("a=" + a);
6: }
7: public static void main(String[] args) {
8: new Const().test();
9: }
10: }


Compile Const.java and run produces:
 a=500  


Rerun ConstClient without recompilation produces strange result:
 b=600  


Let's take a peek by disassembling Const.class:
1:  Compiled from "Const.java"  
2: public class Const extends java.lang.Object{
3: public static final int VALUE;
4: public Const();
5: Code:
6: 0: aload_0
7: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
8: 4: return
9: public void test();
10: Code:
11: 0: sipush 500
12: 3: istore_1
13: 4: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
14: 7: new #3; //class java/lang/StringBuilder
15: 10: dup
16: 11: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V
17: 14: ldc #5; //String a=
18: 16: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: 19: iload_1
20: 20: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
21: 23: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: 26: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: 29: return
24: public static void main(java.lang.String[]);
25: Code:
26: 0: new #10; //class Const
27: 3: dup
28: 4: invokespecial #11; //Method "<init>":()V
29: 7: invokevirtual #12; //Method test:()V
30: 10: return
31: }


We see that line 11 pushes our new constant value 500 onto the stack.

Let's take another peek at ConstClient.class:
1:  Compiled from "ConstClient.java"  
2: public class ConstClient extends java.lang.Object{
3: public ConstClient();
4: Code:
5: 0: aload_0
6: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
7: 4: return
8: public void test();
9: Code:
10: 0: sipush 600
11: 3: istore_1
12: 4: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
13: 7: new #3; //class java/lang/StringBuilder
14: 10: dup
15: 11: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V
16: 14: ldc #5; //String b=
17: 16: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: 19: iload_1
19: 20: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
20: 23: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
21: 26: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
22: 29: return
23: public static void main(java.lang.String[]);
24: Code:
25: 0: new #10; //class ConstClient
26: 3: dup
27: 4: invokespecial #11; //Method "<init>":()V
28: 7: invokevirtual #12; //Method test:()V
29: 10: return
30: }


Line 10 tells us that for some reason our program pushes 600 onto the stack not 500 - our constant value is still 600! It seems like javac burned the constant value into ConstClient.class instead of JVM resolving it through Const.class!

- "Extra care should be taken when hot-deploying classes with public constants as they could potentially break your code during runtime."

Saturday, September 26, 2009

The Curious Case of Enums and Autoboxing

Prior to the release of JDK 5, the expression in a switch statement must be either a char, byte, short, or int; otherwise, compilation error occurs.

 switch (expression) { 
...
}


However as of JDK 5, Sun introduced enumeration type and auto-boxing and because of this, the switch statement also supports these two concepts.

Below is sample code for JDK 5 and later:

1:  public class CaseEnum {  
2: enum ResultEnum { OK, CANCEL };
3: public void test1() {
4: ResultEnum result = ResultEnum.OK;
5: switch (result) {
6: case OK:
7: case CANCEL:
8: }
9: }
10: public void test2() {
11: switch (new Integer(2)) {
12: case 1:
13: case 2:
14: }
15: }
16: public void test3() {
17: switch (new Byte((byte) 3)) {
18: case 1:
19: case 2:
20: case 3:
21: }
22: }
23: }


Compiling with JDK6 and disassembling this class reveals:

1:  Compiled from "CaseEnum.java"  
2: public class CaseEnum extends java.lang.Object{
3: public CaseEnum();
4: Code:
5: 0: aload_0
6: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
7: 4: return
8: public void test1();
9: Code:
10: 0: getstatic #2; //Field CaseEnum$ResultEnum.OK:LCaseEnum$ResultEnum;
11: 3: astore_1
12: 4: getstatic #3; //Field CaseEnum$1.$SwitchMap$CaseEnum$ResultEnum:[I
13: 7: aload_1
14: 8: invokevirtual #4; //Method CaseEnum$ResultEnum.ordinal:()I
15: 11: iaload
16: 12: lookupswitch{ //2
17: 1: 40;
18: 2: 40;
19: default: 40 }
20: 40: return
21: public void test2();
22: Code:
23: 0: new #5; //class java/lang/Integer
24: 3: dup
25: 4: iconst_2
26: 5: invokespecial #6; //Method java/lang/Integer."<init>":(I)V
27: 8: invokevirtual #7; //Method java/lang/Integer.intValue:()I
28: 11: lookupswitch{ //2
29: 1: 36;
30: 2: 36;
31: default: 36 }
32: 36: return
33: public void test3();
34: Code:
35: 0: new #8; //class java/lang/Byte
36: 3: dup
37: 4: iconst_3
38: 5: invokespecial #9; //Method java/lang/Byte."<init>":(B)V
39: 8: invokevirtual #10; //Method java/lang/Byte.byteValue:()B
40: 11: tableswitch{ //1 to 3
41: 1: 36;
42: 2: 36;
43: 3: 36;
44: default: 36 }
45: 36: return
46: }


From line 14, javac adds an extra command to turn the enum into an integer value by invoking the enum "ordinal" method: CaseEnum$ResultEnum.ordinal:()I

From line 27, javac adds an extra command to unbox Integer to int by invoking the "intValue" method:
java/lang/Integer.intValue:()I

From line 39, again javac adds another extra command to unbox Byte to byte by invoking the "byteValue" method:
java/lang/Byte.byteValue:()B

Java String, StringBuilder and StringBuffer

I had a phone conversation with someone yesterday afternoon about Java's String and StringBuffer and how they work. After 24 hours had elapsed my thoughts on String still comes back to bug me - as if I missed out something.

The urge to scratch this itch finally made me fire up “vi” from my terminal. Yes I know what you are thinking, I'm no "vi" expert, but do I really need Eclipse to write 15 lines of code? :)

Here is the source:

1:  public class Str {  
2: public void sayHelloWorld() {
3: String str1 = "hello " + "world ";
4: String str2 = "hello world again";
5: }
6: public void sayGoodbyeWorld() {
7: String str3 = "goodbye world " + 3;
8: }
9: public void sayYouSayMe() {
10: String str4 = "";
11: for (int i = 0; i < 3; i++) {
12: str4 += "say you say me ";
13: }
14: }
15: }


Compiling the source with JDK 6 followed by
“javap -c Str" gives us:

1:  Compiled from "Str.java"  
2: public class Str extends java.lang.Object{
3: public Str();
4: Code:
5: 0: aload_0
6: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
7: 4: return
8: public void sayHelloWorld();
9: Code:
10: 0: ldc #2; //String hello world
11: 2: astore_1
12: 3: ldc #3; //String hello world again
13: 5: astore_2
14: 6: return
15: public void sayGoodbyeWorld();
16: Code:
17: 0: ldc #4; //String goodbye world 3
18: 2: astore_1
19: 3: return
20: public void sayYouSayMe();
21: Code:
22: 0: ldc #5; //String
23: 2: astore_1
24: 3: iconst_0
25: 4: istore_2
26: 5: iload_2
27: 6: iconst_3
28: 7: if_icmpge 36
29: 10: new #6; //class java/lang/StringBuilder
30: 13: dup
31: 14: invokespecial #7; //Method java/lang/StringBuilder."<init>":()V
32: 17: aload_1
33: 18: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: 21: ldc #9; //String say you say me
35: 23: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: 26: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
37: 29: astore_1
38: 30: iinc 2, 1
39: 33: goto 5
40: 36: return
41: }


The disassembled JDK 6 code from above reveals quite a bit about this simple class.
  • Line 2: the compiler automatically extends our class from java.lang.Object.

  • Line 3-7: the compiler automatically inserts a default Str constructor that invokes java.lang.Object constructor

  • Line 10: ldc #2 suggests that “hello world” string is stored as the 2nd item from constant pool table, where “hello " and "world” is automagically concatenated for you by the compiler

  • Line 12: ldc #3 suggests that “hello world again” string is stored as the 3rd item from constant pool table

  • Line 17: ldc #4 suggests that “goodbye world 3” is stored as the 4th item from constant pool table, where the compiler again automagically concatenated integer “3” to "goodbye world "

  • Line 22: ldc #5 suggests that our empty string is stored as the 5th item from constant pool table

  • Line 29-36: tells us that javac inserts StringBuilder to concatenate "say you say me " strings with "say you say me " stored as the 9th item from constant pool table

  • Line 36: StringBuilder object invokes the toString method and assigns it back to “str4”





Let's try compiling the same source with JDK 1.4 by running “javac -source 1.4 -target 1.4 Str.java” then followed by “javap -c Str” which gives us:

1:  Compiled from "Str.java"  
2: public class Str extends java.lang.Object{
3: public Str();
4: Code:
5: 0: aload_0
6: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
7: 4: return
8: public void sayHelloWorld();
9: Code:
10: 0: ldc #2; //String hello world
11: 2: astore_1
12: 3: ldc #3; //String hello world again
13: 5: astore_2
14: 6: return
15: public void sayGoodbyeWorld();
16: Code:
17: 0: ldc #4; //String goodbye world 3
18: 2: astore_1
19: 3: return
20: public void sayYouSayMe();
21: Code:
22: 0: ldc #5; //String
23: 2: astore_1
24: 3: iconst_0
25: 4: istore_2
26: 5: iload_2
27: 6: iconst_3
28: 7: if_icmpge 36
29: 10: new #6; //class java/lang/StringBuffer
30: 13: dup
31: 14: invokespecial #7; //Method java/lang/StringBuffer."<init>":()V
32: 17: aload_1
33: 18: invokevirtual #8; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
34: 21: ldc #9; //String say you say me
35: 23: invokevirtual #8; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
36: 26: invokevirtual #10; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
37: 29: astore_1
38: 30: iinc 2, 1
39: 33: goto 5
40: 36: return
41: }



The disassembled code for 1.4 and 1.6 looks the same except that 1.4 uses StringBuffer instead of StringBuilder as StringBuilder was introduced as part of JDK 5 and later. So it looks like a little bit of String optimisation is done on the Javac level not on the JVM level.