I am sure you must be thrilled by the concept of the compact string feature we just learned about. Now let's look at the most common usage of string, which is concatenation. Have you ever wondered what really happens when we try to concatenate two strings? Let's explore. Take the following example:
public static String getMyAwesomeString(){
int javaVersion = 9;
String myAwesomeString = "I love " + "Java " + javaVersion + " high performance book by Mayur Ramgir";
return myAwesomeString;
}
In the preceding example, we are trying to concatenate a few strings with the int value. The compiler will then take your awesome strings, initialize a new StringBuilder instance, and then append all these individuals strings. Take a look at the following bytecode generation by javac. I have used the ByteCode Outline plugin for Eclipse to visualize the disassembled bytecode of this method. You may download it from http://andrei.gmxhome.de/bytecode/index.html:
// access flags 0x9
public static getMyAwesomeString()Ljava/lang/String;
L0
LINENUMBER 10 L0
BIPUSH 9
ISTORE 0
L1
LINENUMBER 11 L1
NEW java/lang/StringBuilder
DUP
LDC "I love Java "
INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
ILOAD 0
INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
LDC " high performance book by Mayur Ramgir"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 1
L2
LINENUMBER 12 L2
ALOAD 1
ARETURN
L3
LOCALVARIABLE javaVersion I L1 L3 0
LOCALVARIABLE myAwesomeString Ljava/lang/String; L2 L3 1
MAXSTACK = 3
MAXLOCALS = 2
Quick Note: How do we interpret this?
- Invokestatic: This is useful for invoking static methods
- Invokevirtual: This uses of dynamic dispatch for invoking public and protected non-static methods
- Invokeinterface: This is very similar to invokevirtual except that the method dispatch is based on an interface type
- Invokespecial: This is useful for invoking constructors, methods of a superclass, and private methods
However, at runtime, due to the inclusion of -XX:+-OptimizeStringConcat into the JIT compiler, it can now identify the append of StringBuilder and the toString chains. In case the match is identified, produce low-level code for optimum processing. Compute all the arguments' length, figure out the final capacity, allocate the storage, copy the strings, and do the in place conversion of primitives. After this, handover this array to the String instance without copying. It is a profitable optimization.
But this also has a few drawbacks in terms of concatenation. One example is that in case of a concatenating string with long or double, it will not optimize properly. This is because the compiler has to do .getChar first which adds overhead.
Also, if you are appending int to String, then it works great; however, if you have an incremental operator like i++, then it breaks. The reason behind this is that you need to rewind to the beginning of the expression and re-execute, so you are essentially doing ++ twice. And now the most important change in Java 9 compact string. The length spell like value.length >> coder; C2 cannot optimize it as it does not know about the IR.
Hence, to solve the problem of compiler optimization and runtime support, we need to control the bytecode, and we cannot expect javac to handle that.
We need to delay the decision of which concatenation can be done at runtime. So can we have just method String.concat which will do the magic. Well, don't rush into this yet as how would you design the method concat. Let's take a look. One way to go about this is to accept an array of the String instance:
public String concat(String... n){
//do the concatenation
}
However, this approach will not work with primitives as you now need to convert each primitive to the String instance and also, as we saw earlier, the problem is that long and double string concatenation will not allow us to optimize it. I know, I can sense the glow on your face like you got a brilliant idea to solve this painful problem. You are thinking about using the Object instance instead of the String instance, right? As you know the Object instance is catch all. Let's look at your brilliant idea:
public String concat(Object... n){
//do the concatenation
}
First, if you are using the Object instance, then the compiler needs to do autoboxing. Additionally, you are passing in the varargs array, so it will not perform optimally. So, are we stuck here? Does it mean we cannot use the preeminent compact string feature with string concatenation? Let's think a bit more; maybe instead of using the method runtime, let javac handle the concatenation and just give us the optimized bytecode. That sounds like a good idea. Well, wait a minute, I know you are thinking the same thing. What if JDK 10 optimizes this further? Does that mean, when I upgrade to the new JDK, I have to recompile my code again and deploy it again? In some cases, its not a problem, in other cases, it is a big problem. So, we are back to square one.
We need something that can be handled at runtime. Ok, so that means we need something which will dynamically invoke the methods. Well, that rings a bell. If we go back in our time machine, at the dawn of the era of JDK 7 it gave us invokedynamic. I know you can see the solution, I can sense the sparkle in your eyes. Yes, you are right, invokedynamic can help us here. If you are not aware of invokedyanmic, let's spend some time to understand it. For those who have already mastered the topic, you could skip it, but I would recommend you go through this again.