Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When would a compiler choose StringBuffer over StringBuilder for String concatenation

Tags:

java

java-8

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:

  1. When is StringBuffer/StringBuilder not implicitly used by the compiler?
  2. What happens when Java Compiler sees many String concatenations in one line?
  3. Java: String concat vs StringBuilder - optimised, so what should I do?
  4. Best practices/performance: mixing StringBuilder.append with String.concat
  5. StringBuilder vs String concatenation in toString() in Java
  6. 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:

  1. When and why would a compiler choose to use StringBuffer instead of StringBuilder?
  2. Wouldn't it make more sense if the compiler had the choice of using any AbstractStringBuilder implementation?
like image 396
mkobit Avatar asked Mar 24 '15 01:03

mkobit


2 Answers

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”.

like image 103
Holger Avatar answered Sep 29 '22 13:09

Holger


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.

like image 33
Brett Okken Avatar answered Sep 29 '22 11:09

Brett Okken