Java中String不可变性是设计契约而非语法限制,需主动适配:拼接用StringBuilder、判等用Objects.equals、敏感信息用char[]、防注入用PreparedStatement。

Java 中 String 的不可变性不是语法限制,而是设计契约——一旦创建,内容和内存地址都不再改变。这个特性本身不直接“提升健壮性”,但若理解不到位,反而容易写出线程不安全、内存浪费或逻辑错误的代码。真正健壮的 String 使用,关键在于主动适配不可变性,而非对抗它。
用 StringBuilder 替代频繁拼接
String 不可变意味着每次 + 或 concat() 都生成新对象。循环中拼接 N 次,就创建 N 个中间 String,时间复杂度 O(n²),堆内存压力陡增。
建议:
- 单次拼接(如
"prefix" + value + ".txt")由编译器自动优化为 StringBuilder,无需干预 - 循环内拼接、条件分支拼接、或方法内多次追加,显式使用
StringBuilder(线程不安全,高效)或StringBuffer(线程安全,有同步开销) - 初始化时预估容量(如
new StringBuilder(1024)),避免数组扩容带来的复制开销
校验入参时优先用 Objects.equals() 判空与相等
String 不可变,但变量仍可为 null。直接调用 str.equals("abc") 在 str 为 null 时抛 NullPointerException,这是日常崩溃高发点。
建议:
- 统一用
Objects.equals(str, "abc")——内部先判 null,再调用 equals,安全且语义清晰 - 判断是否为空字符串或 null,用
StringUtils.isBlank(str)(Apache Commons)或str == null || str.isEmpty(),避免str.length() == 0前未判空 - 不要依赖
==比较字符串内容(除非确定是常量池内同一对象),一律用equals()
敏感信息处理要防“残留”与“误传”
不可变性让 String 无法被清空或覆写,导致密码、token 等敏感内容在堆中驻留时间不可控,可能被 heap dump 泄露。
建议:
- 敏感字符串尽量用
char[]接收(如Console.readPassword()返回 char[]),用完立即手动设为全 '\0':Arrays.fill(pwdArray, '\0') - 避免将敏感内容转成 String 后再处理;若必须,确保 String 对象作用域极小,且不被日志、缓存、异常信息意外捕获
- 日志打印前脱敏:用
logger.info("user: {}, token: ***", userId, mask(token)),而不是直接拼接原始 token
构造参数化 SQL 或 JSON 时杜绝字符串拼接
不可变性不会阻止你写出 "SELECT * FROM user WHERE name = '" + name + "'" 这种代码——但它会让 SQL 注入漏洞更隐蔽、更难审计。
建议:
- 数据库操作一律使用 PreparedStatement 占位符,由 JDBC 驱动安全转义
- 生成 JSON 用 Jackson/Gson 等库的序列化方法,而非手拼
"{\"name\":\"" + name + "\"}" - 模板类场景(如邮件正文)选用成熟模板引擎(Thymeleaf、Freemarker),它们内置转义机制,比自己用 String.format() 更可靠
不可变性不是枷锁,是约束下的确定性。写健壮代码,不是否认它,而是把“每次修改都新建”变成可预期的行为模式——该缓存就缓存,该复用就复用,该销毁就换类型,该防御就加判空。清楚每行 String 代码背后发生了什么,比记住“String 是 final 类”重要得多。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











