String是被final修饰的不可变类,底层用char[](JDK8-)或byte[](JDK9+)存储,所有修改方法均返回新对象,保障线程安全、常量池共享与hashCode缓存。

Java 中的 String 不是基本类型,而是一个被 final 修饰的不可变类,它本质上是字符序列的封装,底层用 char[](JDK 8 及以前)或 byte[](JDK 9+ 做了紧凑字符串优化)存储内容。
为什么说 String 是“不可变”的?
一旦创建,它的内容无法被修改——所有看似修改的方法(如 replace()、substring()、toUpperCase())都返回一个**新对象**,原对象保持不变。
- 这是设计使然:保障线程安全、支持字符串常量池共享、让
hashCode()可缓存(提升HashMap性能) - 常见误操作:
str.replace("a", "b");—— 没赋值,结果直接丢弃;必须写成str = str.replace("a", "b"); - 副作用明显:循环中用
+=拼接,每次都在堆上新建对象,GC 压力大,JDK 8+ 虽有编译器优化,但逻辑上仍是低效路径
字符串怎么创建?字面量和 new 到底差在哪?
两种主流方式行为差异极大,直接影响内存布局和 == 判断结果:
-
String s1 = "hello";→ 查字符串常量池,存在则复用,否则新建并入池;推荐用于已知内容的场景 -
String s2 = new String("hello");→ 总是在堆上新建对象,即使池里已有相同内容;s1 == s2一定为false - JDK 7+ 起,字符串常量池移到堆内存中,但语义没变:池内仍去重,且只对字面量和显式
intern()的字符串生效
== 和 equals() 到底该怎么选?
用错就等于埋雷,尤其在判空、网络/数据库返回值、配置读取等场景:
立即学习“Java免费学习笔记(深入)”;
-
==比较的是引用地址,仅当两个变量指向同一对象时才为true(比如都来自字面量且内容相同) -
equals()比较的是内容,但若调用方为null,会抛NullPointerException - 安全写法:
"expected".equals(str)(字面量在前),或 JDK 7+ 的Objects.equals(str1, str2) - 忽略大小写?用
equalsIgnoreCase(),别自己转大小写再比
trim() 真的能“去空格”吗?
不能一概而论——它只处理 ASCII 控制字符:' '、'\t'、'\n'、'\r'、'\f',不处理全角空格、Unicode 分隔符(如 \u2000~\u200F)等。
- JDK 11+ 引入
strip(),真正按 Unicode 标准识别空白,更健壮 - 判断是否为空白串,别只用
str.trim().isEmpty(),要先防null:str == null || str.strip().isEmpty() - 需要更细粒度控制?可配合正则:
str.replaceAll("^\\s+|\\s+$", "")(注意\\s在 JDK 中默认含 Unicode 空白)
字符串看着简单,但常量池复用逻辑、不可变性带来的赋值习惯、编码隐含的乱码风险、正则方法里的转义陷阱……每个点都容易在上线后突然冒头。写的时候多想一层“它到底在内存哪”,比事后 debug 快得多。









