元数据卡
- 前置知识:面向对象(第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(新对象)运行步骤:
- "hello" 字符串本身完全没动
- toUpperCase() 内部创建新字符串 "HELLO"
- s 仍然指向 "hello",upper 指向新串
预期输出:
hello
HELLO为什么这么设计 —— 四个现实原因:
- 字符串常量池 —— 同一个 "hello" 被多处引用,可变就全乱了
- 线程安全 —— 不可变对象可以在线程间自由传递,不需要同步
- HashMap 的 key —— String 缓存了 hashCode(算一次存起来),如果可变,放 HashMap 后哈希值变了就找不到了
- 安全性 —— 类加载器、网络连接依赖字符串,可变则能中途篡改
字面量 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); // ?运行步骤:
a = "hello":JVM 检查常量池,没有就创建,a 指向池中对象b = "hello":发现池已有,直接指向同一个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。