String 是「铁憨憨」:用双引号写的都是 String 对象;它自己永远不可变,一切"修改"都返回新对象;比内容一律 equals——常量池和 == 的坑 4.5 埋的钩子这节收网。
4.5 Object类(equals vs == 的本体 + String 常量池坑的出处)、6.1 包装类("比内容用 equals"刚立过规矩)、第二章 Java编程基础/2.3 Java中的运算符(+ 拼接的雏形)。
1. String 是什么?——用引号写的都是对象
String s = "abc"; // "abc" 本身就是一个 String 对象
String t = new String("abc"); // 也能 new,但不推荐(原因见下)
之前我们一直在用 String 却没正式介绍它:打印、拼接、new Person("波仔") 传名字……现在补上户口。它的三个身份:
- 引用类型:String 是类,变量里存的是地址不是内容
- 不可变(Immutable):对象内容一旦生成就改不了
- 自带常量池:字面量创建时进"字符串池"复用
2. 两种创建方式:房间分配完全不同
String s1 = "abc"; // 字面量:常量池里找/建 "abc",s1 指过去
String s2 = "abc"; // 池里已有,直接共用 → s1 == s2 为 true
String s3 = new String("abc"); // new:堆里再开一间新房 → s1 == s3 为 false
| 方式 | 内存里发生什么 | 建议 |
|---|---|---|
"abc" 字面量 |
常量池查重,有就复用 | 日常用这个 |
new String("abc") |
堆里造新对象 + 常量池可能再建一间,一个 new 造两个对象 | 别用,浪费 |
"abc".equals(输入) 优于 输入.equals("abc")——把已知的常量写在前面,输入是 null 时返回 false 而不是炸 NPE(6.1 判空思路的 String 版)。
3. 不可变:String 是「铁憨憨」
String s = "abc";
s.replace('a', 'x'); // 白调了!返回的新对象没人接
System.out.println(s); // abc——原对象纹丝不动
s = s.replace('a', 'x'); // 用变量接住返回值
System.out.println(s); // xbc——s 改指了新对象,旧的"abc"等回收
没有哪个方法能改老对象的内容——所有"修改类"方法(replace/substring/toUpperCase…)都是返回新对象。这就是为什么方法必须接返回值:s = s.replace(...)。
① 常量池复用的前提(内容会变就没法共用);② 多线程下天然安全;③ 字符串用得极多,JVM 敢做深度优化。先知道结论,原理到后面章节自然懂。
4. == 与 equals:老规矩再上岗
==比地址(是不是同一个对象)equals比内容
常量池的巧合(s1 == s2 为 true)不能依赖——就像 6.1 里 127 的巧合一样。规矩:比字符串内容,永远写 equals,别用 ==。(4.5 埋的坑 → 6.1 包装类 → 本节,三连收网。)
5. 常用 API 速查(10 个,用熟够本)
String s = "Hello,Java";
s.length() // 10:长度(数组的 length 没括号,String 的有!)
s.charAt(1) // 'e':按下标取字符
s.equals("Hello,Java") // true:比内容
s.substring(6) // "Java":从下标 6 截到尾
s.substring(0, 5) // "Hello":[0,5) 含头不含尾
s.indexOf("Java") // 6:找位置,找不到返回 -1
s.replace("Java", "World") // "Hello,World":替换(返回新对象)
s.split(",") // ["Hello", "Java"]:按逗号切成数组
s.toUpperCase() // "HELLO,JAVA"
s.trim() // 去掉两端空格
① substring(0, 5) 含头不含尾——取下标 0~4,下标 5 不在内。
② charAt(下标) 越界 → StringIndexOutOfBoundsException(5.2 收容所名单又 +1)。
6. 拼接:+ 能用,但别在循环里狂用
String msg = "姓名:" + name + ",成绩:" + score; // 少量拼接,+ 舒服
+ 每拼一次就造一批新对象。循环里拼几百上千次,内存里全是垃圾——这种场景请 6.3 的 StringBuilder 出场(铺垫已埋)。
7. 动手环节(IDEA06 · Demo02)
- 写
Demo02,用字面量和 new 各创建一个 "abc",分别打印==和equals的结果。成功标准:false/true/true/false 四个结果说得清谁是谁。 - 30 秒实验 ①:不接返回值直接调
s.replace('a','x'),打印 s → 还是 "abc",亲眼看"铁憨憨"。 - 30 秒实验 ②:
s.substring(0, 5)打印 → 数一数含头不含尾。 - 30 秒实验 ③:
s.charAt(100)→ StringIndexOutOfBoundsException,读报错三段。 - 30 秒实验 ④:
"abc".equals(输入)vs输入.equals("abc"),输入传 null 对比——一个 false 一个 NPE。 - 综合小任务:把
"波仔,86,宜-male"用 split 拆开,取出成绩转成 int(6.1 的 parseInt 上岗)。
8. 常见误区
- 用 == 比字符串内容 → 常量池巧合会骗你,一律 equals。
- 以为 s.replace(...) 能改 s 自己 → 不接返回值等于白调,String 不可变。
- substring 当成"含头含尾" → [1,3) 取下标 1、2,不含 3。
- 数组的 length 和 String 的 length() 混淆 → 数组是属性没括号,String 是方法带括号。
- 循环里用 + 疯狂拼接 → 每次造新对象,该 StringBuilder 上场了。
9. 本节小结
| 要点 | 内容 |
|---|---|
| 身份 | 引用类型 + 不可变 + 自带常量池 |
| 创建 | 字面量(推荐,进池复用)vs new(造两个对象,别用) |
| 不可变 | 所有"修改"返回新对象,必须用变量接 |
| 比较 | == 比地址、equals 比内容,永远 equals |
| API | length / charAt / substring / indexOf / replace / split 十件套 |
| 拼接 | 少量用 +,循环狂拼等 StringBuilder |
10. 可折叠自测题
true。字面量 + 字面量在编译期就合并成 "ab",直接指向常量池同一个对象(编译器的优化)。但若是两个 String 变量相加,则是运行时造新对象,== 就可能是 false——又见"巧合不可依赖"。
下标 2、3、4、5,共 4 个字符——含头 [2,不含尾 6)。
"yes".equals(input)——常量放前面,input 为 null 时返回 false 不炸 NPE。反过来写 input 为 null 就 NPE(String 是引用类型,null 上调方法必炸,5.1 空指针老朋友)。
ABC。toLowerCase 返回新对象但没人接,s 纹丝不动——不可变铁律。想变就 s = s.toLowerCase();。
String data = "语文=90,数学=85,英语=88";
String[] parts = data.split(",");
int sum = 0;
for (String p : parts) { // 增强 for 遍历(2.7 学的)
String[] kv = p.split("=");
sum += Integer.parseInt(kv[1]); // 6.1 parseInt 上岗
}
System.out.println("总分:" + sum); // 263
串起本节 split + 6.1 parseInt + 2.7 遍历——字符串和数字的接力棒传完了。
11. 下一步学什么
- ☑ 6.3 System类 ✅ —— 不能 new 的系统工具箱:秒表实测循环拼接的费、exit 停机、arraycopy 搬数组。点此复习
相关笔记
- 第六章 Java API/6.1 包装类 ← 上一篇:"比内容用 equals"的规矩刚立,本节 String 版
- 4.5 Object类:equals vs == 的本体课,常量池坑的出处
- 2.7 数组:toCharArray / split 返回数组,又是数组的地盘
- 5.2 运行时异常与编译时异常:StringIndexOutOfBoundsException 进收容所名单