Saturday, November 1, 2014

String concatenation vs StringBuilder in Java

Lets consider two ways of concatenating Strings in Java.
The most common way to do so is to use operator +:
public String concatenateWithPlus(String s1, String s2){
  return "concatenated=" + s1 + s2;
}
Another way is to use StringBuilder:
public String concatenateWithStringBuilder(String s1, String s2){
  final StringBuilder result = new StringBuilder();
  result.append("concatenated=");
  result.append(s1);
  result.append(s2);
  return result.toString();
}
The question is which method is faster and consumes less resources?

It seems natural to think that + operator creates a new instance of a String every time it is applied. However, lets look into the bytecodes generated for these two snippets.
  public java.lang.String concatenateWithPlus(java.lang.String, java.lang.String);
    Code:
   0: new           #2  // class java/lang/StringBuilder
   3: dup
   4: invokespecial #3  // Method java/lang/StringBuilder."":()V
   7: ldc           #4  // String concatenated=
   9: invokevirtual #5  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  12: aload_1
  13: invokevirtual #5  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  16: aload_2
  17: invokevirtual #5  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  20: invokevirtual #6  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  23: areturn
  public java.lang.String concatenateWithStringBuilder(java.lang.String, java.lang.String);
    Code:
   0: new           #2  // class java/lang/StringBuilder
   3: dup
   4: invokespecial #3  // Method java/lang/StringBuilder."":()V
   7: astore_3
   8: aload_3
   9: ldc           #4  // String concatenated=
  11: invokevirtual #5  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  14: pop
  15: aload_3
  16: aload_1
  17: invokevirtual #5  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  20: pop
  21: aload_3
  22: aload_2
  23: invokevirtual #5  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  26: pop
  27: aload_3
  28: invokevirtual #6  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  31: areturn
As we see the compiler uses StringBuilder in the bytecode for both cases! So both variants don't create unnecessary String instances, and moreover, the second case requires two more instructions per concatenation (pop and aload_3), therefore is slower than the first one!

Take away from this example is that we should look into the bytecode more often and verify our assumptions with the compiler.

PS:
These are the commands to generate bytecode from a Java source:
javac MyClass.java
javap -c MyClass > MyClass.bc

Wednesday, October 29, 2014

Multiple java versions on Mac OS

I like alternatives utility on linux which allows managing multiple versions of the same application. Unfortunately Mac OS does not have such utility therefore one needs to manage dependencies on his own. For java dev such important dependency is obviously java.

It took me a while to understand the directory structure of java versions 6, 7 and 8 which are installed on a newly released Mac OS 10.10 Yosemite.

It is known that Oracle does not support java 6 for Mac OS and instead Apple has its own version: http://support.apple.com/kb/dl1572

After installing it or updating to Yosemite the java directory layout looks like that:

$ which java
/usr/bin/java

$ ls -laF /usr/bin/java
lrwxr-xr-x 1 root wheel 77 Oct 29 20:34 /usr/bin/java@ -> /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java

$ ls -laF /System/Library/Frameworks/JavaVM.framework/Versions
drwxr-xr-x 11 root wheel 374 Oct 29 20:33 ./
drwxr-xr-x 10 root wheel 340 Oct 29 20:20 ../
lrwxr-xr-x 1 root wheel 10 Oct 29 20:20 1.4@ -> CurrentJDK
lrwxr-xr-x 1 root wheel 10 Oct 29 20:20 1.4.2@ -> CurrentJDK
lrwxr-xr-x 1 root wheel 10 Oct 29 20:20 1.5@ -> CurrentJDK
lrwxr-xr-x 1 root wheel 10 Oct 29 20:20 1.5.0@ -> CurrentJDK
lrwxr-xr-x 1 root wheel 10 Oct 29 20:20 1.6@ -> CurrentJDK
lrwxr-xr-x 1 root wheel 10 Oct 29 20:20 1.6.0@ -> CurrentJDK
drwxr-xr-x 7 root wheel 238 Oct 29 20:20 A/
lrwxr-xr-x 1 root wheel 54 Oct 29 20:27 Current@ -> /System/Library/Frameworks/JavaVM.framework/Versions/A
lrwxr-xr-x 1 root wheel 58 Oct 29 20:33 CurrentJDK@ -> /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents


After installing java 7 and 8 JDK, they land to the following directories:

$ ls -laF /Library/Java/JavaVirtualMachines/
drwxr-xr-x 5 root wheel 170 Oct 29 20:39 ./
drwxrwxr-x 5 root wheel 170 Oct 29 20:20 ../
drwxr-xr-x 3 root wheel 102 Oct 23 21:28 jdk1.7.0_71.jdk/
drwxr-xr-x 3 root wheel 102 Oct 29 20:39 jdk1.8.0_25.jdk/


Note the path difference between /Library/Java/JavaVirtualMachines/ for java 7, 8 and /System/Library/Java/JavaVirtualMachines for java 6

Now we need to make these versions live together and provide flexibility for choosing necessary default java version.
One can do it by following next two steps.

Step 1: Map CurrentJDK to the necessary version

I need java 7 to be default, so

$ sudo rm -f /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK
$ sudo ln -s /Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk/Contents /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK

Step 2: Remap /usr/bin/ to /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home/bin/

I have written a small python2.7 script for migrating binaries to CurrentJDK: remap java.
python remap_java.py
NOTE: the script requires root privileges

That is it!

For making default another version, we need to repeat the first step. For example, to change to java 8 do:

$ sudo rm -f /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK
$ sudo ln -s /Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK

$ java -version
java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)


Happy coding!