Assuming that I need to initiate a String and then update it for a couple a times more, like in this pseudo code:
String s = "first";
if (<some condition>) { s += "second"; }
if (<some condition>) { s += "third"; }
if (<some condition>) { s += "fourth"; }
In case that I have about 4 times in maximum, is it better to use it that way or just simply use StringBuffer/StringBuilder ?
Actually what i'm asking is, in case of which updating times of a String (which is a mutable type) is the efficient way to use the above options?
First, String is not a mutable class. Every time you use +=, another object is returned.
Second, unless you use that in a tight loop, it is not very important anyway.
Third, although it may not be a computer science-like approach, if I try to use
StringBuilder s = new StringBuilder("first");
s.append( "second" );
s.append( "third" );
s.append( "fourth" );
Intellij suggests to replace it with a String, and that suggestion only appears if it is at least as efficient to use a simple String (although it may not be exactly the same thing because of the conditions)
According to Java: String concat vs StringBuilder - optimised, so what should I do?
the Java compiler does replace a series of String concatenations with String builders:
public static void main(String[] args) {
String s = "first";
s+=" second" ;
if(args.length >0)
s+=" third";
System.out.println("s, = " + s);
}
Using JDK 1.8.0_66 and using javap -c SbTest.class the following output is produced. Although I am not an expert on byte code, it seems that multiple instances of StringBuilder are created (3,28). So as suggested in the linked answers, the compiled code seems to be something like:
String s = "first";
s = new StringBuilder().append(s).append(" second") ;
if(args.length >0)
s = new StringBuilder().append(s).append(" third") ;
So unless the JIT is optimizing this, it might still be more efficient to use the StringBuilder yourself.
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String first
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6 // String second
16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_1
23: aload_0
24: arraylength
25: ifle 48
28: new #3 // class java/lang/StringBuilder
31: dup
32: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
35: aload_1
36: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
39: ldc #8 // String third
41: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
44: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
47: astore_1
48: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
51: new #3 // class java/lang/StringBuilder
54: dup
55: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
58: ldc #10 // String s, =
60: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
63: aload_1
64: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
67: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
70: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
73: return
String s = "first";
if (<some condition>) { s += "second"; }
if (<some condition>) { s += "third"; }
if (<some condition>) { s += "fourth"; }
Is the right approach because the compiler will turn it into a StringBuilder
(JDK 1.6 and above)
That said, the optimization is the same unless you are using it inside a loop.
See this link for more info
If you are working with reasonably sized strings (like the words you have there) using the string class is completely fine, and using a string builder class won't be much faster than using a normal string. You want to replace it when you might be dealing with huge strings (think many many paragraphs) and repeatedly changing them. StringBuilder and StringBuffer should work about the same, but StringBuffer is thread safe, so if you are going to write multithreaded code then use StringBuffer.
The only way to know is to benchmark.
package com.example;
import static org.junit.Assert.*;
import java.time.Duration;
import java.time.Instant;
import org.junit.Test;
public class ExampleTest {
#Test
public void test() {
for (int j = 0; j < 4; j++) {
Instant start = Instant.now();
for (int i = 0; i < 10000; i++) {
// Try various implementations here...
String s = "first";
if (true) {
s += "second";
}
if (true) {
s += "third";
}
if (true) {
s += "fourth";
}
}
System.out.println("Took: " + Duration.between(start, Instant.now()).toMillis() + " millis");
}
}
}
If performance turns out not to be an issue then optimise for the human and make it as readable and maintainable as possible.
Related
I just encountered this decompiled class file of my class:
MyClass
while ((line = reader.readLine()) != null) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
i++;
}
}
The while loop has been changed to a for loop in the class file:
Decompiled MyClass
for (String[] colArr = null; (line = reader.readLine()) != null; ++i) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
}
}
Why has this loop been changed to a for?
I think it might be another way of code optimization by the compiler, I could be wrong.
I just wanted to know if it is, what advantages does a for loop provide over a while loop or other loop?
What is the category of such code optimizations?
In this situation changing while() to for() is not an optimization. There is simply no way to know from bytecode which one was used in a source code.
There are many situations when:
while(x)
is the same as:
for(;x;)
Suppose we have a three similar java applications - one with while() statement, and two with corresponting for(). First for() with stopping criterion only like in the standard while(), and second for() also with iterator declaration and incrementation.
APPLICATION #1 - SOURCE
public class While{
public static void main(String args[]) {
int i = 0;
while(i<5){
System.out.println(i);
i++;
}
}
}
APPLICATION #2 - SOURCE
public class For{
public static void main(String args[]) {
int i = 0;
for(; i<5 ;){
System.out.println(i);
i++;
}
}
}
APPLICATION #3 - SOURCE
public class For2{
public static void main(String args[]) {
for(int i=0;i<5;i++){
System.out.println(i);
}
}
}
If we compile all of them we have got:
APPLICATION #1 - BYTECODE
public class While {
public While();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 20
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
14: iinc 1, 1
17: goto 2
20: return
}
APPLICATION #2 - BYTECODE
public class For {
public For();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 20
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
14: iinc 1, 1
17: goto 2
20: return
}
APPLICATION #3 - BYTECODE
public class For2 extends java.lang.Object{
public For2();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 20
7: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
14: iinc 1, 1
17: goto 2
20: return
}
So you can see, there is no difference associated with for and while usage.
As others have already pointed out: The decompiler (usually) cannot distinguish between different source codes that result in the same byte code.
Unfortunately, you did not provide the full code of the method. So the following contains some guesses about where and how this loop appears inside a method (and these guesses might, to some extent, distort the result).
But let's have a look at some roundtrips here. Consider the following class, containing methods with both versions of the code that you posted:
import java.io.BufferedReader;
import java.io.IOException;
import java.util.regex.Pattern;
public class DecompileExample {
public static void methodA(BufferedReader reader) throws IOException {
String line = null;
int i = 0;
while ((line = reader.readLine()) != null) {
System.out.println("line: " + line);
if (i == 0) {
String[] colArr = line.split(Pattern.quote("|"));
} else {
i++;
}
}
}
public static void methodB(BufferedReader reader) throws IOException {
String line = null;
int i = 0;
for (String[] colArr = null; (line = reader.readLine()) != null; ++i) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
}
}
}
}
Compiling it with
javac DecompileExample.java -g:none
will create the corresponding class file. (Note: The -g:none parameter will cause the compiler to omit all debug information. The debug information might otherwise be used by the decompiler to reconstruct a more verbatim version of the original code, particularly, including the original variable names)
Now looking at the byte code of both methods, with
javap -c DecompileExample.class
will yield the following:
public static void methodA(java.io.BufferedReader) throws java.io.IOException;
Code:
0: aconst_null
1: astore_1
2: iconst_0
3: istore_2
4: aload_0
5: invokevirtual #2 // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
8: dup
9: astore_1
10: ifnull 61
13: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
16: new #4 // class java/lang/StringBuilder
19: dup
20: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
23: ldc #6 // String line:
25: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: aload_1
29: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
32: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
35: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
38: iload_2
39: ifne 55
42: aload_1
43: ldc #10 // String |
45: invokestatic #11 // Method java/util/regex/Pattern.quote:(Ljava/lang/String;)Ljava/lang/String;
48: invokevirtual #12 // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
51: astore_3
52: goto 4
55: iinc 2, 1
58: goto 4
61: return
and
public static void methodB(java.io.BufferedReader) throws java.io.IOException;
Code:
0: aconst_null
1: astore_1
2: iconst_0
3: istore_2
4: aconst_null
5: astore_3
6: aload_0
7: invokevirtual #2 // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
10: dup
11: astore_1
12: ifnull 60
15: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
18: new #4 // class java/lang/StringBuilder
21: dup
22: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
25: ldc #6 // String line:
27: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: aload_1
31: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
37: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: iload_2
41: ifne 54
44: aload_1
45: ldc #10 // String |
47: invokestatic #11 // Method java/util/regex/Pattern.quote:(Ljava/lang/String;)Ljava/lang/String;
50: invokevirtual #12 // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
53: astore_3
54: iinc 2, 1
57: goto 6
60: return
}
(There is a small difference: The String[] colArr = null is translated into an
aconst null
astore_3
at the beginning of the second version. But this is one of the aspects that is related to parts of the code that you have omitted in the question).
You did not mention which one you are using, but the JD-GUI decompiler from http://jd.benow.ca/ decompiles this into the following:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.regex.Pattern;
public class DecompileExample
{
public static void methodA(BufferedReader paramBufferedReader)
throws IOException
{
String str = null;
int i = 0;
while ((str = paramBufferedReader.readLine()) != null)
{
System.out.println("line: " + str);
if (i == 0) {
String[] arrayOfString = str.split(Pattern.quote("|"));
} else {
i++;
}
}
}
public static void methodB(BufferedReader paramBufferedReader)
throws IOException
{
String str = null;
int i = 0;
String[] arrayOfString = null;
while ((str = paramBufferedReader.readLine()) != null)
{
System.out.println("line: " + str);
if (i == 0) {
arrayOfString = str.split(Pattern.quote("|"));
}
i++;
}
}
}
You can see that the code is the same for both cases (at least regarding the loop - there one more is a difference regarding the "dummy variables" that I had to introduce in order to compile it, but this is unrelated to the question, so to speak).
The tl;dr message is clear:
Different source codes can be compiled into the same byte code. Consequently, the same byte code can be decompiled into different source codes. But every decompiler has to settle for one version of the source code.
(A side note: I was a bit surprised to see that when compiling without -g:none (that is, when the debug information is retained), JD-GUI even somehow manages to reconstruct that the first one used a while-loop and the second one used a for-loop. But in general, and when the debug information is omitted, this is simply no longer possible).
That's basically because of the nature of bytecode. Java bytecode is something like assembly language, so there are no such things as for and while loop, there is simply jump instruction: goto. So there may be no difference between while and for loop, Both can be compiled to similar code and decompiler is just making guess.
Both the for loop and the while loop code segments can be translated into similar machine code. After that when de-compiling the de-compiler has to pick one of the two possible scenarios.
I guess that is what's happening here.
simply:
compile(A) -> C
compile(B) -> C
So when you are given C, then there should be a guess to pick A or B
In the following code, When it reaches the comment, if the GC is not run, approximately 1000 object is created(according to OCA book), the StringBuilder is modified and remains as one object, the empty string " " is pooled and re-used, that's all that is explained. isn't the argument s a new String("s") that needs to be GCed, and i , will it not be converted to a new String object first, then combined with " " creates another new String object making them 2 String objects at that line, eligible for GC along with the append's argument, a total of 3 String object in every loop. so a sum of 3000 object when the code reaches the comment line?
public class Mounds {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
String s = new String();
for (int i = 0; i < 1000; i++) {
s = " " + i;
sb.append(s);
}
// done with loop
}
}
If we compile this code and look at the generated bytecode we can examine that exactly
public static void main(java.lang.String[]) throws java.io.IOException;
Code:
0: new #19 // class java/lang/StringBuilder
3: dup
4: invokespecial #21 // Method java/lang/StringBuilder."<init>":()V
7: astore_1
8: new #22 // class java/lang/String
11: dup
12: invokespecial #24 // Method java/lang/String."<init>":()V
15: astore_2
16: iconst_0
17: istore_3
18: goto 47
21: new #19 // class java/lang/StringBuilder
24: dup
25: ldc #25 // String
27: invokespecial #27 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
30: iload_3
31: invokevirtual #30 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
34: invokevirtual #34 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
37: astore_2
38: aload_1
39: aload_2
40: invokevirtual #38 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: pop
44: iinc 3, 1
47: iload_3
48: sipush 1000
51: if_icmplt 21
54: return
The instructions we care about are from 21 to 40. In 21, there is a second StringBuilder created, we will come back to that later.
In 25 We see, that there is a ldc, which means that a literal is pushed to the stack, in this case it is the literal String " ".
Then the real magic happens. The constructor of the second StringBuilder is called, which takes the literal from the stack as argument. Then the int i is loaded from the local variable array with iload_3, after that the append method of the second StringBuilder is called to append that i to it, and then the toString is called. With astore_2 and aload_1 the return value of the toString call is stored, and the first StringBuilder is loaded, and after that the String is loaded again. And finally the append method of the first StringBuilder is called to add that new String to the StringBuilder.
So it turns out, that ther is a new StringBuilder created in every loop, because everytime you use " " + i a StringBuilder has to be created to concatenate the String and int. Additionally a new String will be created by the toString method of the intermediate StringBuilder, so there will be a total of 2000 Objects there.
A better version would look like this:
for (int i = 0; i < 1000; i++) {
sb.append(' ');
sb.append(i);
}
That will create the following bytecode:
public static void main(java.lang.String[]) throws java.io.IOException;
Code:
0: new #19 // class java/lang/StringBuilder
3: dup
4: invokespecial #21 // Method java/lang/StringBuilder."<init>":()V
7: astore_1
8: new #22 // class java/lang/String
11: dup
12: invokespecial #24 // Method java/lang/String."<init>":()V
15: astore_2
16: iconst_0
17: istore_3
18: goto 37
21: aload_1
22: bipush 32
24: invokevirtual #25 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
27: pop
28: aload_1
29: iload_3
30: invokevirtual #29 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
33: pop
34: iinc 3, 1
37: iload_3
38: sipush 1000
41: if_icmplt 21
44: return
We can see, that there now is only one StringBuilder, which gets its append method called twice, so no memory is allocated here and this should be better.
The optimal usage would be:
StringBuilder sb = new StringBuilder(4000);
for (int i = 0; i < 1000; ++i) {
sb.append(' ').append(i);
}
... do something with sb.toString()
As:
String s = new String(); creates an unnecessary empty string. Same as String s = "";. (Optimization not considered.)
s = " " + i; concatenates two strings into a new String. A task one should leave to the StringBuilder, as that is the sole purpose of it.
Appending a char ' ' is more efficient than a String " ".
new StringBuilder(4000) with an initial capacity one could use here, preventing intermittent reallocation on appending. 1000 numbers of which 900 are 3 digits, plus a space, will fit in 4000 chars.
the compiler probably realises that the scope of variable s is inside the loop so it inlines the assignment into the append() to produce
sb.append(" " + i)
so now only the conversion of the int creates a new String in each iteration.
I was looking at the String Javadoc when I noticed this bit about String concatenation:
The Java language provides special support for the string
concatenation operator ( + ), and for conversion of other objects to
strings. String concatenation is implemented through the
StringBuilder(or StringBuffer)
From the Java 8 JLS 15.8.1, it is a choice given to the compiler (emphasis mine):
An implementation may choose to perform conversion and concatenation
in one step to avoid creating and then discarding an intermediate
String object. To increase the performance of repeated string
concatenation, a Java compiler may use the StringBuffer class or a
similar technique to reduce the number of intermediate String objects
that are created by evaluation of an expression.
I made a small program to see what it compiles down to
public class Tester {
public static void main(String[] args) {
System.out.println("hello");
for (int i = 1; i < 5; i++) {
String s = "hi " + i;
System.out.println(s);
}
String t = "me";
for (int i = 1; i < 5; i++) {
t += i;
System.out.println(t);
}
System.out.println(t);
}
}
And the output when running javap -c Tester shows that StringBuilder is being used:
Compiled from "Tester.java"
public class Tester {
public Tester();
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: ldc #3 // String hello
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: iconst_1
9: istore_1
10: iload_1
11: iconst_5
12: if_icmpge 48
15: new #5 // class java/lang/StringBuilder
18: dup
19: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
22: ldc #7 // String hi
24: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: iload_1
28: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
31: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: astore_2
35: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
38: aload_2
39: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: iinc 1, 1
45: goto 10
48: ldc #11 // String me
50: astore_1
51: iconst_1
52: istore_2
53: iload_2
54: iconst_5
55: if_icmpge 90
58: new #5 // class java/lang/StringBuilder
61: dup
62: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
65: aload_1
66: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
69: iload_2
70: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
73: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
76: astore_1
77: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
80: aload_1
81: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
84: iinc 2, 1
87: goto 53
90: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
93: aload_1
94: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
97: return
}
I've looked at a handful of questions that tell the story that StringBuilder is generally faster because of synchronization in StringBuffer, and that is substituted for these string concatenations:
When is StringBuffer/StringBuilder not implicitly used by the compiler?
What happens when Java Compiler sees many String concatenations in one line?
Java: String concat vs StringBuilder - optimised, so what should I do?
Best practices/performance: mixing StringBuilder.append with String.concat
StringBuilder vs String concatenation in toString() in Java
StringBuilder and StringBuffer
So, considering the things I've read that show that StringBuilder is generally the better choice leads me to wonder a couple things:
When and why would a compiler choose to use StringBuffer instead of StringBuilder?
Wouldn't it make more sense if the compiler had the choice of using any AbstractStringBuilder implementation?
The specification’s wording you have cited origins from older specifications. The simple answer is, before Java 1.5 aka Java 5 there was no StringBuilder. So older compilers didn’t have to choose between StringBuffer and StringBuilder and the specification at that time simply recommended using what is available.
With Java 5, StringBuilder was introduced which doesn’t use synchronized methods which is perfect for the use case of String concatenation as that’s a pure local operation. So for compilers targeting 1.5 or higher, there is a choice (which is still covered by the specification’s words “or a similar technique”) and they will choose StringBuilder as there is no reason for using StringBuffer when targeting 1.5 or higher.
Starting with Java 9, there is a new technology using an invokedynamic bytecode instruction with a bootstrap method from the StringConcatFactory class, but it’s still covered by the specification’s words “or a similar technique”.
It depends on the compiler implementation (javac is not the only compiler). However, there is never a good case for using StringBuffer over StringBuilder for these types of uses. It is always a single use narrowly scoped object where the synchronization of StringBuffer provides no functional value.
So the short answer is that no compiler /should/ ever use StringBuffer.
I read Jon Skeet's answer about concatenating strings with +. I wonder whether the compiler also recognizes appending constand strings with a StringBuffer/StringBuilder.
This code for constructing a URL has a good intention:
StringBuffer sb = new StringBuffer(constant1);
sb.append(nonconstant);
sb.append("?");
sb.append(constant2);
sb.append("=");
sb.append(constant3);
sb.append("&");
sb.append(constant4);
sb.append("=");
sb.append(constant5);
However, if Stringbuffer.append() is not optimized by the compiler for constants, I'd say the following code would be more efficient:
StringBuffer sb = new StringBuffer(constant1);
sb.append(non-constant);
sb.append("?" + constant2 + "=" + constant3 + "&" + constant4 + "=" + constant5);
because the compiler would optimize the + string concatenation at compile time.
Why not try it out? In java 1.7, the main method of the following class:
public class Concat1
{
private static final String constant2 = "c2";
private static final String constant3 = "c3";
public void main(String[] args)
{
StringBuilder sb = new StringBuilder();
sb.append(args[0]);
sb.append("?");
sb.append(constant2);
sb.append("=");
sb.append(constant3);
System.out.println(sb.toString());
}
}
(I changed the number of constant for clarity) yields the following byte code:
public class Concat1 {
public Concat1();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."":()V
7: astore_2
8: aload_2
9: aload_1
10: iconst_0
11: aaload
12: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: pop
16: aload_2
17: ldc #5 // String ?
19: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: pop
23: aload_2
24: ldc #6 // String c2
26: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
29: pop
30: aload_2
31: ldc #7 // String =
33: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: aload_2
38: ldc #8 // String c3
40: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: pop
44: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
47: aload_2
48: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
51: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
54: return
}
while the following class:
public class Concat2
{
private static final String constant2 = "c2";
private static final String constant3 = "c3";
public void main(String[] args)
{
StringBuilder sb = new StringBuilder();
sb.append(args[0]);
sb.append("?" + constant2 + "=" + constant3);
System.out.println(sb.toString());
}
}
is compiled to:
public class Concat2 {
public Concat2();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."":()V
7: astore_2
8: aload_2
9: aload_1
10: iconst_0
11: aaload
12: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: pop
16: aload_2
17: ldc #5 // String ?c2=c3
19: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: pop
23: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
30: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
33: return
}
So apparently, you are right. In the second class the append method of the StringBuilder is only called twice, while in the first case it is called for each constant string.
Does the compiler optimize Stringbuffer/StringBuilder.append() for constant strings?
No.
However, I think that the premise of your proposed optimization is incorrect. I suggest that you take the two versions of the code and compile them. Then use javap to see what the compiled code looks like in each case.
(FWIW, I expect that your "optimization" will not improve the generated code. It will most likely create a second StringBuilder to concatenate the intermediate string, and convert that into a String. You end up with roughly the same number of append operations, plus the creation of an extra temporary StringBuilder and an extra temporary String.)
I have noticed that Java compiler does not converting String addition (+) to StringBuilder.append() method. I have created a class which has only one method
public void doSomething(String a, String b) {
String c = a + "a";
String d = b + "b";
String e = c + d;
String f = e;
System.out.println(f);
}
After compilation and decompilation my method looked like this:
public void doSomething(String paramString1, String paramString2)
{
String str1 = paramString1 + "a";
String str2 = paramString2 + "b";
String str3 = str1 + str2;
String str4 = str3;
System.out.println(str4);
}
Why compiler not optimizing my code? I am using ant for packaging and debug setting is false. I've also tried javac for single java file but result is the same.
Your decompiler is indeed simplifying the code.
Consider this source file:
public class foo {
public void a(String[] args) {
String b = (new StringBuilder()).append("x").append(args[0]).toString();
}
public void b(String[] args) {
String b = "x" + args[0];
}
}
javap output:
public class foo {
public foo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void a(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: ldc #4 // String x
9: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
12: aload_1
13: iconst_0
14: aaload
15: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
21: astore_2
22: return
public void b(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: ldc #4 // String x
9: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
12: aload_1
13: iconst_0
14: aaload
15: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
21: astore_2
22: return
}
See that the bytecodes are essentially identical: the compiler has transformed method b into method a using the StringBuilder.append optimization.
Now let's see what JD says:
public class foo
{
public void a(String[] paramArrayOfString)
{
String str = "x" + paramArrayOfString[0];
}
public void b(String[] paramArrayOfString) {
String str = "x" + paramArrayOfString[0];
}
}
That's right: JD actually takes the function a and interprets it as being a string addition, even though the original source code was specified using an explicit StringBuilder!
Therefore, we can see that JD will try to reverse the StringBuilder optimization if it detects that pattern being used.
I did javap -c Test.class and StringBuilder appeared (Java 8).
public void doSomething(java.lang.String, java.lang.String);
Code:
0: new #2 // class StringBuilder
3: dup
4: invokespecial #3 // Method StringBuilder."<init>":()V
7: aload_1
8: invokevirtual #4 // Method StringBuilder.append:(LString;)LStringBuilder;
11: ldc #5 // String a
13: invokevirtual #4 // Method StringBuilder.append:(LString;)LStringBuilder;
16: invokevirtual #6 // Method StringBuilder.toString:()LString;
19: astore_3
20: new #2 // class StringBuilder
23: dup
24: invokespecial #3 // Method StringBuilder."<init>":()V
27: aload_2
28: invokevirtual #4 // Method StringBuilder.append:(LString;)LStringBuilder;
31: ldc #7 // String b
33: invokevirtual #4 // Method StringBuilder.append:(LString;)LStringBuilder;
36: invokevirtual #6 // Method StringBuilder.toString:()LString;
39: astore 4
41: new #2 // class StringBuilder
44: dup
45: invokespecial #3 // Method StringBuilder."<init>":()V
48: aload_3
49: invokevirtual #4 // Method StringBuilder.append:(LString;)LStringBuilder;
52: aload 4
54: invokevirtual #4 // Method StringBuilder.append:(LString;)LStringBuilder;
57: invokevirtual #6 // Method StringBuilder.toString:()LString;
60: astore 5
62: aload 5
64: astore 6
66: getstatic #8 // Field System.out:Ljava/io/PrintStream;
69: aload 6
71: invokevirtual #9 // Method java/io/PrintStream.println:(LString;)V
74: return
I think the decompiler tries to simplify this to achieve a natural coding.
The Java compiler doesn't do any optimization at compile time because that's left for the JIT at runtime. If you want to build up a string efficiently across multiple statements, you should use a StringBuilder.
The + operator compiles into calls on a StringBuilder object. When you write:
String c = a + "a";
the compiler produces code as if you'd written:
String c = new StringBuilder().append(a).append("a").toString();
If you use + several times within a single expression, such as a + "a" + b + "b", the compiler will use a single StringBuilder for the whole expression, calling append as many times as necessary. But it doesn't optimize multiple statements into a single equivalent expression, so if you want to use a single StringBuilder to join all your strings together, you'll need to either write it as a single expression or use StringBuilder explicitly in your code.