Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Saturday, February 6, 2016

Java 7 and Multi-Catch Exception

Java 7 has been out for a few years and I admit this is one of the features I seem to neglect. Perhaps, I've been living under a rock, yeah?

I've got JDK 8 installed and because of my sheer laziness, I'll stick to this version of the compiler to analyse how multi-catch exception is implemented.

She saw first some bracelets, then a pearl necklace, then a Venetian gold cross set with precious stones, of admirable workmanship. She tried on the ornaments before the mirror, hesitated and could not make up her mind to part with them, to give them back.

She kept asking: "Haven't you any more?"

Let's define a few exceptions before dipping a toe in the water.

PearlNecklaceException.java
package org.lyeung.thenecklace;

public class PearlNecklaceException extends RuntimeException {
    // do-nothing
}

VenetianCrossException.java
package org.lyeung.thenecklace;

public class VenetianCrossException extends RuntimeException {
    // do-nothing
}

DiamondNecklaceException.java
package org.lyeung.thenecklace;

public class DiamondNecklaceException extends RuntimeException {
    // do-nothing
}

Followed by the main class we'll look at:
Mathilde.java
package org.lyeung.thenecklace;

public class Mathilde {

    private void retrieveNecklace(int value) throws PearlNecklaceException,
            VenetianCrossException, DiamondNecklaceException {
        if (value == 0) {
            throw new PearlNecklaceException();
        } else if (value == 1) {
            throw new VenetianCrossException();
        } else {
            throw new DiamondNecklaceException();
        }
    }

    public void findNecklace1(int value) {
        try {
            retrieveNecklace(value);
        } catch (PearlNecklaceException e) {
            e.printStackTrace();
        } catch (VenetianCrossException e) {
            e.printStackTrace();
        } catch (DiamondNecklaceException e) {
            e.printStackTrace();
        }
    }

    public void findNecklace2(int value) {
        try {
            retrieveNecklace(value);
        } catch (PearlNecklaceException e) {
            handleException(e);
        } catch (VenetianCrossException e) {
            handleException(e);
        } catch (DiamondNecklaceException e) {
            handleException(e);
        }
    }

    private void handleException(Exception e) {
        e.printStackTrace();
    }

    public void findNecklace3(int value) {
        try {
            retrieveNecklace(value);
        } catch (VenetianCrossException
                | PearlNecklaceException
                | DiamondNecklaceException  e) {
            e.printStackTrace();
        }
    }
}


All three variants of find necklaces call retrieveNecklace which throws different exceptions base on input value.

The first variant of find necklace is pretty old school. We declare different catch blocks for each exception thrown.

The second variant of find necklace, similar to the first variant, but minimises code duplication by moving the print stack trace into a common method.

The third variant uses multi-catch exception that results to a more readable and more compact code.

Under the hood, these variants produce different bytecodes.


The disassembled code for the first variant:
public void findNecklace1(int);
  Code:
     0: aload_0
     1: iload_1
     2: invokespecial #8                  // Method retrieveNecklace:(I)V
     5: goto          29
     8: astore_2
     9: aload_2
    10: invokevirtual #9                  // Method org/lyeung/thenecklace/PearlNecklaceException.printStackTrace:()V
    13: goto          29
    16: astore_2
    17: aload_2
    18: invokevirtual #10                 // Method org/lyeung/thenecklace/VenetianCrossException.printStackTrace:()V
    21: goto          29
    24: astore_2
    25: aload_2
    26: invokevirtual #11                 // Method org/lyeung/thenecklace/DiamondNecklaceException.printStackTrace:()V
    29: return
  Exception table:
     from    to  target type
         0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
         0     5    16   Class org/lyeung/thenecklace/VenetianCrossException
         0     5    24   Class org/lyeung/thenecklace/DiamondNecklaceException

The exception table says,  "For program counter offset 0 to 5, inclusive and exclusive respectively, the program counter should jump to target offset 8 if PearlNecklaceException is thrown, 16 if VenetianCrossException is thrown and 24 if DiamondNecklaceException is thrown."

All offsets 8, 16 and 24 invoke the caught exception's printStackTrace() method.

The disassembled code for the second variant:
public void findNecklace2(int);
  Code:
     0: aload_0
     1: iload_1
     2: invokespecial #8                  // Method retrieveNecklace:(I)V
     5: goto          32
     8: astore_2
     9: aload_0
    10: aload_2
    11: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
    14: goto          32
    17: astore_2
    18: aload_0
    19: aload_2
    20: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
    23: goto          32
    26: astore_2
    27: aload_0
    28: aload_2
    29: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
    32: return
  Exception table:
     from    to  target type
         0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
         0     5    17   Class org/lyeung/thenecklace/VenetianCrossException
         0     5    26   Class org/lyeung/thenecklace/DiamondNecklaceException

The exception table is not much different from the first variant.

The disassembled code for the last variant:
public void findNecklace3(int);
    Code:
       0: aload_0
       1: iload_1
       2: invokespecial #8                  // Method retrieveNecklace:(I)V
       5: goto          13
       8: astore_2
       9: aload_2
      10: invokevirtual #14                 // Method java/lang/RuntimeException.printStackTrace:()V
      13: return
    Exception table:
       from    to  target type
           0     5     8   Class org/lyeung/thenecklace/VenetianCrossException
           0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
           0     5     8   Class org/lyeung/thenecklace/DiamondNecklaceException
}

Now, the exception table looks different. As you can see, the target values all point at 8, where offset 8 prints the stack trace!

The generated bytecodes are definitely smaller and more compact than the previous variants.


Below is the disassembled code for entire Mathilde class:
Compiled from "Mathilde.java"
public class org.lyeung.thenecklace.Mathilde {
  public org.lyeung.thenecklace.Mathilde();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return

  private void retrieveNecklace(int) throws org.lyeung.thenecklace.PearlNecklaceException, org.lyeung.thenecklace.VenetianCrossException, org.lyeung.thenecklace.DiamondNecklaceException;
    Code:
       0: iload_1
       1: ifne          12
       4: new           #2                  // class org/lyeung/thenecklace/PearlNecklaceException
       7: dup
       8: invokespecial #3                  // Method org/lyeung/thenecklace/PearlNecklaceException."":()V
      11: athrow
      12: iload_1
      13: iconst_1
      14: if_icmpne     25
      17: new           #4                  // class org/lyeung/thenecklace/VenetianCrossException
      20: dup
      21: invokespecial #5                  // Method org/lyeung/thenecklace/VenetianCrossException."":()V
      24: athrow
      25: new           #6                  // class org/lyeung/thenecklace/DiamondNecklaceException
      28: dup
      29: invokespecial #7                  // Method org/lyeung/thenecklace/DiamondNecklaceException."":()V
      32: athrow

  public void findNecklace1(int);
    Code:
       0: aload_0
       1: iload_1
       2: invokespecial #8                  // Method retrieveNecklace:(I)V
       5: goto          29
       8: astore_2
       9: aload_2
      10: invokevirtual #9                  // Method org/lyeung/thenecklace/PearlNecklaceException.printStackTrace:()V
      13: goto          29
      16: astore_2
      17: aload_2
      18: invokevirtual #10                 // Method org/lyeung/thenecklace/VenetianCrossException.printStackTrace:()V
      21: goto          29
      24: astore_2
      25: aload_2
      26: invokevirtual #11                 // Method org/lyeung/thenecklace/DiamondNecklaceException.printStackTrace:()V
      29: return
    Exception table:
       from    to  target type
           0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
           0     5    16   Class org/lyeung/thenecklace/VenetianCrossException
           0     5    24   Class org/lyeung/thenecklace/DiamondNecklaceException

  public void findNecklace2(int);
    Code:
       0: aload_0
       1: iload_1
       2: invokespecial #8                  // Method retrieveNecklace:(I)V
       5: goto          32
       8: astore_2
       9: aload_0
      10: aload_2
      11: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
      14: goto          32
      17: astore_2
      18: aload_0
      19: aload_2
      20: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
      23: goto          32
      26: astore_2
      27: aload_0
      28: aload_2
      29: invokespecial #12                 // Method handleException:(Ljava/lang/Exception;)V
      32: return
    Exception table:
       from    to  target type
           0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
           0     5    17   Class org/lyeung/thenecklace/VenetianCrossException
           0     5    26   Class org/lyeung/thenecklace/DiamondNecklaceException

  private void handleException(java.lang.Exception);
    Code:
       0: aload_1
       1: invokevirtual #13                 // Method java/lang/Exception.printStackTrace:()V
       4: return

  public void findNecklace3(int);
    Code:
       0: aload_0
       1: iload_1
       2: invokespecial #8                  // Method retrieveNecklace:(I)V
       5: goto          13
       8: astore_2
       9: aload_2
      10: invokevirtual #14                 // Method java/lang/RuntimeException.printStackTrace:()V
      13: return
    Exception table:
       from    to  target type
           0     5     8   Class org/lyeung/thenecklace/VenetianCrossException
           0     5     8   Class org/lyeung/thenecklace/PearlNecklaceException
           0     5     8   Class org/lyeung/thenecklace/DiamondNecklaceException
}


Wednesday, October 28, 2015

Elwood sources now available in Bitbucket

For the past few weeks, I've been squeezing every last drop of my own time to get Elwood working. From a simple process executor to a functional web application, where I could trigger a build by clicking a link. I have to say that this app is simple and crude, but is really fun to work on!

Think about this for a moment - we're writing a tool to build the tool itself!

Elwood is made up of a back-end REST service and a front-end AngularJS app. The REST service requires Redis 3 (never tested this on Redis 2+). The app keeps a list of projects and tracks the build results. Currently, it only supports Git with SSH (with or without passphrase).

"Elwood threw down his fork. He pushed his chair back and stood up. Liz stared up in astonishment at him, her cup half raised. Bob's mouth hung open, his sentence unfinished. Little Toddy said nothing."

The sources are available in Bitbucket:



 Looking ahead, this is going to be a very long journey...

Tuesday, August 18, 2015

Elwood - Building the build server (a prototype)

Things are coming together right now! It all started from an imaginary concept filled with dragons, knights and fairy god-mothers - well yeah, you know what I mean.

At the most basic level, a build server has to satisfy 3 requirements:
  • Get the source code
  • Build the code
  • Report if the build is successful

What I have right now is a work in progress. It's quite rudimentary and clunky. It clones your sources (currently Git only) and runs your build command (e.g. mvn clean package). The UI constantly polls for the log but goes endlessly. I have to manually kill the poller to stop this activity - how shameful of me!
He builds a boat in his back yard, to great annoyance to his wife and neighbors. When they ask him why he builds it, he tells them he doesn't know.

What's next?
I need to capture and store the build number, as well as the build result.

Saturday, August 27, 2011

Keeping Barbarians at the Gate

My workstation sits at the further end of five workstations aligned adjacently in a row. The walls of the entire floor are painted in white, seemingly dull but immaculate. The dark grey carpet covered across the floor adds sombre to the yet harsh cold August winter.

Looking at the distant window from where I sit,
        I know it’s another bleak day.


I can’t remember if I had seen sunlight two or three days ago. Perhaps four? Maybe there was one yesterday. Seriously, I can’t recall and I can’t be bothered at all.

I’m glued to my screen that I can’t hear the noise around me – because I’m busy keeping the barbarians at the gate.


class User {
private TimeInterval accessibleTime;
private Set<String> accessCodes;

public User(TimeInterval accessibleTime) {
this.accessibleTime = accessibleTime;
}
}


Add null pointer check to parameter and ensure the API validates against incorrect time interval:

public User(TimeInterval accessibleTime) {
if (accessibleTime== null) {
throw new IllegalArgumentException(
"accessibleTime parameter cannot be null");
if (accessibleTime.getFromTime() > accessibleTime.getToTime()) {
throw new IllegalArgumentException("accessibleTime time from [" + acc
essibleTime.getTimeFrom() + "] is later than time to [" + accessibleTime.getTime
To() + "]");
this.accessibleTime = accessibleTime;
}


Generate getter and setter methods:

@YetAnotherNotNullValidation(message = "access codes cannot be null")
public String getAccessCodes() {
return accessCodes;
}

public String setAccessCodes(Set<String> accessCodes) {
this.accessCodes = accessCodes;
}



Hang on, should I expose this setter method even though I have a validation annotation of NotNull hoisted up there? It should be alright I guess, as the validation framework should catch this during runtime. But then again, it requires extra effort to check null values.


// Someone accidentally wipes out accessCodes somewhere in the code
user.setAccessCodes(null);

// and I need to add null check to prevent NPE
if (user.getAccessCodes() == null) {
user.setAccessCodes(new HashSet<String>());
}


Do I want to see this sprinkled everywhere? Perhaps not. It is easy to misuse the setter method and leave accessCode variable in limbo. Better get rid of this, I would rather not have "Midas Touch" effect here.


doSomething(user.getAccessCodes());
// to clear out values
user.getAccessCodes().clear();


Sunday, February 21, 2010

I Wanna Be A JavaDoc Writer

Dear Sir or Madam will you read my JavaDoc?
It took me 'while to write, agonise will you take a look?
It's based on a spec by a person very dear
And I need a job and I wanna be a JavaDoc writer,
JavaDoc writer...


Do you write JavaDocs? I'm not sure how productive this would be to you, but I personally find writing JavaDoc for trivial methods, especially those requires a little thinking, ensures that I fully understood what I need to do before I start my Pee Wee's big adventure.

Once upon a time, on one lazy Friday afternoon, I restricted myself with TDD approach, that is write test code before implementation. It occurred to me that my input field will be populated by a name thus, I need to break them down into first name and last name. Easy-peasy, invoke split method from String class and pass space character as delimeter! The first element of returned String array will always represent the first name and second element will always represent the last name.

Before I started the name-parser method implementation, I wrote a small documentation right on top instead. Since I never had a poetic license in my entire life, let alone enrolled myself in three semesters of English-lit during my Uni days, my JavaDoc documentation was, is and will never be written as technical, as formal as I want it to be. I always end up with mediocre technical English documentation. I know I'm useless.

"Oh Uncle Bob, where art thou?"

One thing struck me as I happily started typing my doco - clackity-clack, what happens when someone calls himself "Donkey Kong Jr."? Clackity-clack.

(For argument's sake, let's put the spec aside for now.)

Surely, I can assume his first name is "Donkey" and last name is "Kong Jr." What about "King Henry VIII" and "Don Quixote de la Mancha"?

Hmmm, doesn't sound right, does it...?

I found myself dumbstruck at that particular instance, not to the problem nor to the solution, but to myself - a sudden self-pity, self-realisation of how naive and simple-minded I was.

So, do you want to be a JavaDoc writer?

Clackity-clack.

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

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.