StringBuilder vs 字符串拼接在 toString() 中:底层工作原理
\n\n您是否曾想过,为什么在 Stack Overflow 上关于如何在 Java 中正确编写 toString() 的争论至今仍在继续?一些开发者坚信 StringBuilder 是唯一正确的途径。另一些人则轻松地使用普通的加号拼接,毫不担心。谁才是对的?
让我们深入 Java 编译器和 JIT(即时)优化器的底层。您将了解到,当您编写 str1 + str2 时实际发生了什么,以及为什么在 2024 年这个问题的答案可能会让您感到惊讶。
为什么这个话题如此热门?
\n\n“StringBuilder vs String concatenation in toString()”这个问题在 Stack Overflow 上获得了数千次浏览,这并非偶然。每个 Java 开发者都至少编写过一次 toString() 方法。并且每个人都面临过选择:是通过加号优雅地编写,还是通过 StringBuilder“正确”地编写。
关于 Java 字符串拼接 总是很慢的误解已经存在了 20 多年。在 Java 1.4 和 Java 5 时代,这确实是事实。但编译器并没有停滞不前。
\n\nJava 规范怎么说?
\n\n根据 Java 语言规范(JLS §15.18.1),字符串的 + 运算符是语法糖。编译器必须将字符串拼接转换为高效的代码。从 Java 5(J2SE 5.0)开始,javac 编译器会自动将拼接替换为使用 StringBuilder。
让我们在实践中验证这一点。
\n\n代码示例:简单的 toString()
\n\n我们来看一个经典示例——Person 类的 toString() 方法:
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\n看起来简洁且可读。但在字节码中是什么样子呢?
\n\n深入字节码
\n\n编译这个类,并使用 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\n看到了吗?编译器自己创建了 StringBuilder! 它创建了一个新的 StringBuilder 对象,为字符串的每个部分调用 append(),然后调用 toString()。这与您手动编写的代码相同,但源代码更具可读性。
那么显式使用 StringBuilder 呢?
\n\n现在,我们编写一个显式使用 StringBuilder 的版本:
@Override\npublic String toString() {\n Strin