Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

StringBuffer and String usage scenario in Java

I can implement single method in two ways,

1.

public String getTestMessage()
{
    return "Hello" + "World..!";
}

2.

public String getTestMessage()
{
    return new StringBuffer("Hello").append("World..!").toString();
}

In first scenario, there will be two new String objects will be created.
In second scenario also there will be two new objects created, but there will be one String and StringBuffer. Now way will be fast from them? I am little bit confused.

like image 624
Vishal Zanzrukia Avatar asked Jan 27 '26 05:01

Vishal Zanzrukia


1 Answers

Between your two scenarios, option 1 will be faster every time, unless the JITC does something I don't expect. I really don't expect it to make a difference, though, unless you call those methods extremely frequently.

Why? Because you actually don't create new objects with option 1. The compiler should perform constant folding, turning "Hello" + "World..!" into "HelloWorld..!". And because this is a compile-time constant String, it's automatically interned into the String pool at VM startup. So every time that method is called, you're just getting a reference to that "canonical" String. No object creation is performed.

In option 2, you always create multiple objects -- the StringBuffer (you should be using StringBuilder, by the way), the backing char[], and the result String (at the very least). And doing that in a tight loop is not very efficient.

In addition, option 1 is more readable, which is always something you should consider when writing code.

Proof:

Given this test code:

public class Test {
    public String getTestMessageA() {
        return "Hello" + "World..!";
    }

    public String getTestMessageB() {
        return new StringBuffer("Hello").append("World..!").toString();
    }
}

Compiling with javac -XD-printflat shows us what this code is processed to before compilation to bytecode:

public class Test {

    public Test() {
        super();
    }

    public String getTestMessageA() {
        return "HelloWorld..!";
    }

    public String getTestMessageB() {
        return new StringBuffer("Hello").append("World..!").toString();
    }
}

Notice how "Hello" + "World..!" was converted at compile time to a single String. So String concatenation is not what is happening in the first option.

Now let's look at the bytecode. Here's the constant pool:

Constant pool:
   #1 = Methodref          #10.#20        //  java/lang/Object."<init>":()V
   #2 = String             #21            //  HelloWorld..!
   #3 = Class              #22            //  java/lang/StringBuffer
   #4 = String             #23            //  Hello
   #5 = Methodref          #3.#24         //  java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
   #6 = String             #25            //  World..!
   #7 = Methodref          #3.#26         //  java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
   #8 = Methodref          #3.#27         //  java/lang/StringBuffer.toString:()Ljava/lang/String;

Here's the bytecode for option 1:

  public java.lang.String getTestMessageA();
    Code:
       0: ldc           #2                  // String HelloWorld..!
       2: areturn 

Well, that's short. As you can see, the JVM loads a constant (ldc) from the pool and returns it. No object created directly in the method.

Now here's the bytecode for option 2:

public java.lang.String getTestMessageB();
    Code:
       0: new           #3                  // class java/lang/StringBuffer
       3: dup           
       4: ldc           #4                  // String Hello
       6: invokespecial #5                  // Method java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
       9: ldc           #6                  // String World..!
      11: invokevirtual #7                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
      14: invokevirtual #8                  // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
      17: areturn   

So this code creates a new StringBuffer, loads the appropriate constants from the string pool, calls the append() method for each of them, then calls the toString() method on the buffer. The toString() is implemented as such:

@Override
public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}

So option 2 will create two new objects every time you call it, and also executes more instructions. Thus, option 1 will be faster.

like image 181
awksp Avatar answered Jan 29 '26 19:01

awksp



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!