跳到内容

元数据卡

  • 前置知识:面向对象(第7-8章)
  • 预计时间:15 分钟
  • 完成标志:理解 String 不可变性、字符串池概念,能正确区分 == 和 equals

你的进度

老陈师傅叫住你:"你天天在用 String,但你真的懂它吗?"

你以为字符串就是 "hello" —— 直到发现 s.toUpperCase() 没改变 s 本身。字符串看上去基础,踩坑起来一点都不含糊。这一页说清楚 String 的本质:为什么不可变、存在哪里、怎么比。


String 是一个特殊的对象

Java 有八种基本类型:int、long、double、boolean…… String 不在其中。String 是引用类型

java
String name = "小陈";                  // 字面量,像基本类型一样方便
类型存储方式比较方式能否修改
int栈上直接存数值== 比数值
String栈存引用,堆存对象equals() 比值不可变

String 是引用类型,但创建语法像基本类型。两个世界各占一半——这就是第一个坑的根源。


为什么 String 不可变

java
public final class String {
    private final byte[] value;        // final + 无 setter = 创建后不能改
}

final byte[] value + 类本身 final + 不暴露修改方法 —— 三个措施实现了不可变性。

java
String s = "hello";
String upper = s.toUpperCase();

System.out.println(s);       // hello(没变)
System.out.println(upper);   // HELLO(新对象)

运行步骤:

  1. "hello" 字符串本身完全没动
  2. toUpperCase() 内部创建新字符串 "HELLO"
  3. s 仍然指向 "hello",upper 指向新串

预期输出:

hello
HELLO

为什么这么设计 —— 四个现实原因:

  1. 字符串常量池 —— 同一个 "hello" 被多处引用,可变就全乱了
  2. 线程安全 —— 不可变对象可以在线程间自由传递,不需要同步
  3. HashMap 的 key —— String 缓存了 hashCode(算一次存起来),如果可变,放 HashMap 后哈希值变了就找不到了
  4. 安全性 —— 类加载器、网络连接依赖字符串,可变则能中途篡改

字面量 vs new String()

java
String a = "hello";
String b = "hello";
String c = new String("hello");

System.out.println(a == b);     // ?
System.out.println(a == c);     // ?

运行步骤:

  1. a = "hello":JVM 检查常量池,没有就创建,a 指向池中对象
  2. b = "hello":发现池已有,直接指向同一个
  3. c = new String("hello"):不管池有没有,堆上 new 一个全新的

预期输出:

true    // a 和 b 指向同一个池对象
false   // c 是堆上新对象

实战原则: 99% 直接用字面量,不要 new String()。


字符串池(String Pool)

java
String s1 = "java";
String s2 = "java";              // 复用池中同一个对象

JVM 有一块叫"字符串常量池"的内存区域,存放字符串字面量:

  • 编译期收集所有双引号字面量
  • 运行时每次创建字面量,先查池 —— 有则复用,无则新建
  • 好处:同一内容只存一份,节省内存

字符串比较:== vs equals

java
String a = "hello";
String b = "hello";
String c = new String("hello");

System.out.println(a == b);          // ?
System.out.println(a == c);          // ?
System.out.println(a.equals(c));     // ?

预期输出:

true    // a 和 b 是同一个池对象
false   // c 是堆上另一个对象
true    // equals 比较字符序列,内容相同

核心规则:== 比引用,equals 比值。

java
// 正确写法
if ("admin".equals(userInput)) { ... }

// 错误写法 —— 大部分时间对,但用户输入时是 new String,== 返回 false
if ("admin" == userInput) { ... }

空指针安全写法:把已知常量放前面:

java
"hello".equals(str);      // str 为 null → false,安全
str.equals("hello");      // str 为 null → NullPointerException,危险

旅人笔记

String 是引用类型但用字面量创建。不可变意味着每次"修改"都是新生对象。字面量进字符串池复用,new 强制新分配。比较字符串永远用 equals(),永远把已知值放前面。

-> 下一步:字符串操作

知道 String 是什么了,接下来看怎么用 —— substring、split、replace、trim,还有拯救拼接性能的 StringBuilder。

看字符串操作 ->

Built with VitePress | Software Systems Atlas