StringBuilder vs concatenation in toString(): how it works under the hood
\n\nHave you ever wondered why debates are still raging on Stack Overflow about how to properly write toString() in Java? Some developers firmly believe that StringBuilder is the only correct way. Others calmly use regular concatenation with the plus sign and don't worry. Who is right?
Let's look under the hood of the Java compiler and the JIT (Just-In-Time) optimizer. You will learn what actually happens when you write str1 + str2 and why in 2024 the answer to this question might surprise you.
Why is this topic so popular?
\n\nThe question "StringBuilder vs String concatenation in toString()" has garnered thousands of views on Stack Overflow for a reason. Every Java developer has written the toString() method at least once. And everyone has faced the choice: write it neatly with the plus sign or "correctly" with StringBuilder.
The myth that string concatenation in Java is always slow has been alive for over 20 years. And it was true... in the era of Java 1.4 and Java 5. But compilers haven't stood still.
\n\nWhat does the Java specification say?
\n\nAccording to the Java Language Specification (JLS §15.18.1), the + operator for strings is syntactic sugar. The compiler is obligated to transform string concatenation into efficient code. Starting from Java 5 (J2SE 5.0), the javac compiler automatically replaces concatenation with the use of StringBuilder.
Let's verify this in practice.
\n\nCode example: a simple toString()
\n\nLet's take a classic example — the toString() method for the Person class:
public class Person {\n private String name;\n private int age;\n private String email;\n\n @Override\n public String toString() {\n return "Person{" +\n "name='" + name + '\\'' +\n ", age=" + age +\n ", email='" + email + '\\'' +\n '}';\n }\n}\n\n\nIt looks clean and readable. But what does it look like in bytecode?
\n\nLooking into the bytecode
\n\nLet's compile this class and look at the bytecode using javap -c Person.class:
public java.lang.String toString();\n Code:\n 0: new #7 // class java/lang/StringBuilder\n 3: dup\n 4: invokespecial #8 // Method StringBuilder."<init>":()V\n 7: ldc #9 // String "Person{name='"\n 9: invokevirtual #10 // StringBuilder.append(String)\n 12: aload_0\n 13: getfield #3 // Field name:Ljava/lang/String;\n 16: invokevirtual #10 // StringBuilder.append(String)\n 19: ldc #11 // String "', age="\n 21: invokevirtual #10 // StringBuilder.append(String)\n 24: aload_0\n 25: getfield #4 // Field age:I\n 28: invokevirtual #12 // StringBuilder.append(int)\n 31: ldc #13 // String ", email='"\n 33: invokevirtual #10 // StringBuilder.append(String)\n 36: aload_0\n 37: getfield #5 // Field email:Ljava/lang/String;\n 40: invokevirtual #10 // StringBuilder.append(String)\n 43: ldc #14 // String "'}"\n 45: invokevirtual #10 // StringBuilder.append(String)\n 48: invokevirtual #15 // StringBuilder.toString():String\n 51: areturn\n\n\nSee? The compiler created StringBuilder itself! It creates a new StringBuilder object, calls append() for each part of the string, and then toString(). The same thing you would write manually, but with more readable source code.
What about explicit StringBuilder?
\n\nNow let's write a version with an explicit StringBuilder:
@Override\npublic String toString() {\n Strin