京东一面这个题啊,其实挺常见的:为啥 IDEA 老是提醒你“可以用 + 替换掉这个 StringBuilder”?很多人第一反应是:不是一直都说多用 StringBuilder 性能更好么,这不是自相矛盾吗?
咱就按面试聊天那种节奏,从你平时写代码说起,一步步拆开它背后发生了啥。
一、先把场景说清楚:IDEA 到底在提醒什么?
一般 IDEA 提示的场景,长这样:
public String buildName(String first, String last) {
StringBuilder sb = new StringBuilder();
sb.append("姓名:");
sb.append(first);
sb.append(" ");
sb.append(last);
return sb.toString();
}
IDEA 会给你一个小灯泡,说类似:
Can be replaced with '+'
然后你一键修复,它会变成:
public String buildName(String first, String last) {
return "姓名:" + first + " " + last;
}
也就是说,它不是让你“永远别用 StringBuilder”,而是:在这种一次性拼接、局部表达式里,用 + 更合适。 那为啥它敢这么建议?核心就一句话:
在大多数简单场景里,
+拼接在编译之后,本来就是StringBuilder。
二、关键点:+ 背后其实也是 StringBuilder(或者更高级的东西)
看一段最简单的代码:
public String demo(String a, String b) {
return a + " - " + b;
}
咱肉眼看到是加号,Javac 编译的时候会把它改写成类似这样的逻辑(伪代码,方便理解):
public String demo(String a, String b) {
StringBuilder sb = new StringBuilder();
sb.append(a);
sb.append(" - ");
sb.append(b);
return sb.toString();
}
也就是说:
-
你自己写 StringBuilder -
你用 +
编译完之后,多数情况下,效果几乎一模一样,只是你手写那版,更啰嗦、可读性更差。
更进一步,从 Java 9 开始,字符串拼接连“固定用 StringBuilder”都不是了,编译器会把 + 变成一个 invokedynamic 调用,底层交给 StringConcatFactory 去优化,可能用到 StringBuilder,也可能用到更聪明的实现(比如提前算出长度、少拷贝几次)。 简单讲:从 JDK 9 开始,手写 StringBuilder 反而不一定比 + 快。
所以 IDEA 的逻辑就很简单:
-
既然编译器已经会帮你造 StringBuilder -
你再自己 new 一个,只会让代码更丑 -
性能又没提升,那就不如用 +,看着清爽点
三、那为什么以前大家都说“用 StringBuilder 性能更好”?
这里要区分两个场景,面试官很喜欢追问的。
1)一次性拼接(IDEA 推荐用 + 的场景)
比如这样:
String msg = "订单号:" + orderId + ", 用户:" + userName + ", 金额:" + amount;
这种是“单个表达式里,把几个东西一股脑儿拼完”,Javac 会特别照顾,自动用到 StringBuilder(或 Java 9 之后的优化方案)。 你自己手写一遍 StringBuilder,本质相当于和编译器“重复劳动”。
2)循环里累加字符串(IDEA 一般不会让你用 +)
经典反例是:
public String join(List<String> list) {
String result = "";
for (String s : list) {
result = result + s;
}
return result;
}
这个就真有性能问题了,因为它会被编译成类似:
for (String s : list) {
result = new StringBuilder()
.append(result)
.append(s)
.toString();
}
也就是每一轮循环都 new 一个新的 StringBuilder,再把原来的字符串复制一遍,复杂度直接奔着 O(n²) 去了,数据量大一点,就明显顶不住。
这时候,正确写法应该是你自己维护一个 StringBuilder:
public String join(List<String> list) {
StringBuilder sb = new StringBuilder();
for (String s : list) {
sb.append(s);
}
return sb.toString();
}
所以总结一下这俩场景:
-
表达式级别、一次性拼接:用 +,编译器自动帮你做好 -
循环里反复拼:自己 new 一个 StringBuilder,IDEA 不会让你改成 +
IDEA 那个提示,其实就是在 第 1 类场景 下才出现的。
四、IDEA 背后的小心思:性能 + 可读性
你可以从“工具作者”的角度想一想:它为啥要提示你?
1)性能没差,甚至 + 更容易被 JDK 优化
在“单个表达式”的场景里:
String s = a + b + c;
和:
StringBuilder sb = new StringBuilder();
sb.append(a).append(b).append(c);
String s = sb.toString();
编译器生成的字节码几乎等价,甚至前者在 Java 9+ 下,用到的是 StringConcatFactory 的优化路径,你手写那堆 append,反而“错过”了一些 JDK 内部的优化机会。
2)+ 对大多数开发者更友好
说句实话,下面两段你更愿意读哪一个?
// 写法 A
return "userId=" + userId + ", name=" + name + ", age=" + age;
// 写法 B
StringBuilder sb = new StringBuilder();
sb.append("userId=").append(userId)
.append(", name=").append(name)
.append(", age=").append(age);
return sb.toString();
在只有 3~5 个字段的情况下,绝大部分人会觉得 A 更直观:左边是字段名,右边是值,一眼就能看出来格式。
IDEA 的设计理念一向是:在性能相同的前提下,更偏向可读性好的代码风格。 所以它才会用 inspection 提醒你,把那种“没必要的手动 StringBuilder”换成 +。
五、面试时怎么说,才能显得你是真懂了?
如果这是京东一面,面试官问你:
为啥 IDEA 建议用
+,而不是 StringBuilder?
你可以这样组织答案,逻辑清楚一点:
-
先表态:
-
在简单的一次性拼接场景里,用 +和手写StringBuilder性能基本一致 -
因为 Javac 会自动把 +编译成StringBuilder或 Java 9 之后的StringConcatFactory调用 -
再点 IDEA 的设计思路:
-
既然性能没差,IDEA 就更偏向可读性好的写法 -
所以才提示你,把那些“局部、一次性使用的 StringBuilder”替换成 + -
顺手补个反例,展示你不是死记硬背:
// 这个场景不能用简单的 '+'
String result = "";
for (String s : list) {
result = result + s; // 这里会频繁 new StringBuilder
}
这个时候你就要说:这里 IDEA 反而会提示我“可以使用 StringBuilder 优化性能”,我会把它改成:
StringBuilder sb = new StringBuilder();
for (String s : list) {
sb.append(s);
}
String result = sb.toString();
-
最后再补一句 Java 9 之后的优化,加点“进阶感”:
-
Java 9 以后, +不再固定翻译成new StringBuilder,而是通过invokedynamic调用StringConcatFactory -
JDK 会根据运行时情况选择更合适的拼接策略,有时比你手写 StringBuilder更聪明
这样整一套下来,性能、编译器行为、IDEA 设计思路、例子、反例都讲到了,面试官一般不会再追着这个问题继续问了。
六、顺手给你几个“该用 + / 该用 StringBuilder”的小参考
1)适合用 + 的:
// 日志
log.info("userId={}, cost={}ms", userId, cost);
// DTO 转字符串
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "'}";
}
// 构造简单提示消息
String msg = "下单成功,订单号:" + orderId + ",金额:" + amount + " 元";
2)适合手动 StringBuilder / StringBuffer 的:
// 循环大批量拼接
StringBuilder sql = new StringBuilder("IN (");
for (int i = 0; i < ids.size(); i++) {
if (i > 0) {
sql.append(",");
}
sql.append("?");
}
sql.append(")");
你在简历或者面试里要表现的是:我不是“要么全 StringBuilder,要么全 +”那种极端派,而是理解编译器和工具的行为,会在对的场景做对的选择。
差不多就这样,这个题本身不难,真的拉开差距的是:你能不能从“IDEA 为啥这么提示”聊到“编译器怎么做的”“JDK9 之后的变化”“不同场景怎么选”,把这些串起来,面试官一般都挺买账的。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
🔥东哥私藏精品🔥
东哥作为一名老码农,整理了全网最全《Java高级架构师资料合集》。总量高达650GB。点击下方公众号回复关键字java 全部免费领取

