Monday, July 6, 2009

Concatenate empty String with Integer or not

Recently I was lucky enough to take part in some damage control mission. At the site I discovered many interesting things. Tomcat was deployed in development mode in production environment and log file was well over 6GB. For some unknown reason they preferred to use Tomcat and solve connection polling manualy (using Proxool). Naturally, the client wanted everything upgraded to Java 6, which may not be such a good idea with Proxool. I hope that one can imagine the amount of work invested in Proxool and appreciate equally the complicated dependencies like Cglib or Asm. Anyway, why would one use Glassfish, TopLink, JSF and EJB 3.0 when with Tomcat we can do nicely our own thread-connection-pulling management, do user interface using Velocity and so on.

Luckily, there was no documentation and the reason for all those architectural decisions can't be clearly established. The unofficial version is that on the same project (but old version) there was a small group of developers using Delphi and Sybase. After years of work they knew business logic so well. Then the client decided to start using Oracle and Java. Since the guys were so familiar with business logic, the fact that they did not know much about Java was not that important. They didn't quite understand the need for build process, source versioning and other boring stuff. So they will check in and out source, do bug fixes, compile at will, drop classes into Tomcat's WEB-INF folder. Mostly people were using Eclipse incremental compiler, but some used ant and javac. Tagging was non-existing and eventually nobody knew what was deployed where - which version of the source. So it was required to discover through decompiling what was deployed in production environment. Through the process I discovered the most original way of converting number to string. Here is the small example of original and common way of obtaining string representation of integer:



public class Main {

public static void main(String[] args) {

System.out.print(useInteger(11));

System.out.print(hereIsYourString(12));

}

static String hereIsYourString(int i){

return ""+i;

}

static String useInteger(int i){

return Integer.toString(i);

}

}


Now some people would maybe like less typing idea more. OK, the question is how does one explain to an inexperienced Java programmer to stay out of those shortcuts?

I think the best way is to disassemble class using the Java Class File Disassembler and take a look at bytecode. The best way to start with javap is this:


~$ javap -help

Usage: javap <options> <classes>...


where options include:

-c Disassemble the code

-classpath <pathlist> Specify where to find user class files

-extdirs <dirs> Override location of installed extensions

-help Print this usage message

-J<flag> Pass <flag> directly to the runtime system

-l Print line number and local variable tables

-public Show only public classes and members

-protected Show protected/public classes and members

-package Show package/protected/public classes

and members (default)

-private Show all classes and members

-s Print internal type signatures

-bootclasspath <pathlist> Override location of class files loaded

by the bootstrap class loader

-verbose Print stack size, number of locals and args for methods

If verifying, print reasons for failure


Using info I will cd to /MyProjects/JavaApplication22/build/classes/javaapplication22 and that is where the product of compilation is. Then I will do dissasembling:


~/MyProjects/JavaApplication22/build/classes/javaapplication22$ javap -c -classpath . Main

Compiled from "Main.java"

public class javaapplication22.Main extends java.lang.Object{

public javaapplication22.Main();

Code:

0: aload_0

1: invokespecial #1; //Method java/lang/Object."<init>":()V

4: return


public static void main(java.lang.String[]);

Code:

0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;

3: bipush 11

5: invokestatic #3; //Method useInteger:(I)Ljava/lang/String;

8: invokevirtual #4; //Method java/io/PrintStream.print:(Ljava/lang/String;)V

11: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;

14: iconst_1

15: invokestatic #5; //Method hereIsYourString:(I)Ljava/lang/String;

18: invokevirtual #4; //Method java/io/PrintStream.print:(Ljava/lang/String;)V

21: return


static java.lang.String hereIsYourString(int);

Code:

0: new #6; //class java/lang/StringBuilder

3: dup

4: invokespecial #7; //Method java/lang/StringBuilder."<init>":()V

7: ldc #8; //String

9: invokevirtual #9; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

12: iload_0

13: invokevirtual #10; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

16: invokevirtual #11; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

19: areturn


static java.lang.String useInteger(int);

Code:

0: iload_0

1: invokestatic #12; //Method java/lang/Integer.toString:(I)Ljava/lang/String;

4: areturn


}


At the first sight output may look confusing, but if we take a better look everything is there and comparing to Java source helps identify our methods. Here you have it: concatenating empty string with integer generates few more instructions than calling static Integer.toString.

If one would like to explore what happens further eg. inside Integer.toString or StringBuilder.append I would warmly recommend downloading OpenJDK, alternatively make a break until such an idea goes away.



No comments:

Post a Comment