5.1 String 的创建与"不可变"
String 是课内最早碰到的引用类型,它的"怪脾气"都来自不可变。
String s1 = "hello"; // ① 字面量:优先用字符串常量池里的对象
String s2 = "hello"; // 与 s1 指向同一个对象
String s3 = new String("hello"); // ② new:一定在堆里新建对象(不复用常量池)
System.out.println(s1 == s2); // true (同一地址)
System.out.println(s1 == s3); // false (不同地址)
System.out.println(s1.equals(s3));// true (内容相同)
不可变性是什么意思
- String 对象一旦创建,内容不能改。所有"修改"操作(拼接、替换、截取)都是造一个新字符串。
s = s + "!"不是把原对象改了,而是新建了一个字符串并让 s 指向它。- 所以循环里大量拼接字符串会产生很多垃圾对象 → 用
StringBuilder(见 5.3)。
易错(含常量池陷阱)
String a = "ab"; String b = "a" + "b";→a == b为 true(编译期就能确定,编译器直接合并成 "ab")。String c = "a"; String d = c + "b";→a == d为 false(运行期拼接,生成新对象)。- 永远用 equals 比内容,别用 ==(== 只比地址,只有你明确想比地址时才用)。
课堂练习(3 题)
"hello"用字面量创建和用new String("hello")创建,区别在哪? 答:字面量优先复用字符串常量池中的对象(可共享);new 一定在堆上新建对象,不共享。- 为什么 String 不可变还常说"修改了字符串"? 答:实际操作是创建新字符串对象并让变量指向它,原对象内容没变。
- 循环里拼接 1 万次字符串,为什么慢? 答:每次 + 都新建 String 对象,产生大量临时垃圾;应改用 StringBuilder.append。
5.2 String 常用方法(查表即可)
不用死背,用多了自然记住——考试重点是 substring 和 split 的下标规则。
| 方法 | 作用 | 例子(s = "Hello Java") |
|---|---|---|
length() | 字符个数 | 10 |
charAt(i) | 取第 i 个字符(从 0) | s.charAt(0) → 'H' |
isEmpty() | 是否为空串 "" | false |
equals(t) | 内容是否相等 | s.equals("Hello Java") → true |
equalsIgnoreCase(t) | 忽略大小写比较 | "abc".equalsIgnoreCase("ABC") → true |
compareTo(t) | 按字典序比较(0 相等,正数大) | "b".compareTo("a") → 1 |
indexOf(t) | 找子串首次出现位置,没有返回 −1 | s.indexOf("Java") → 6 |
lastIndexOf(t) | 最后一次出现位置 | s.lastIndexOf("a") → 9 |
contains(t) | 是否包含子串 | s.contains("Java") → true |
substring(b, e) | 截取 [b, e) 左闭右开 | s.substring(0, 5) → "Hello" |
split(正则) | 按分隔符切成数组 | "a,b,c".split(",") → [a, b, c] |
trim() / strip() | 去首尾空格(strip 支持 Unicode,JDK 11+) | " hi ".trim() → "hi" |
replace(a, b) | 替换字符/子串 | s.replace("Java", "C++") |
toUpperCase()/toLowerCase() | 转大小写 | "abc".toUpperCase() → "ABC" |
String.join(分隔符, …) | 拼接多个串 | String.join("-", "a", "b") → "a-b" |
易错(高频)
- substring(begin, end) 是左闭右开:含 begin 不含 end;长度为
end - begin。 split的参数是正则表达式:按点号切分要写split("\\.")(点号在正则里代表任意字符)。indexOf找不到返回-1,不是 0 也不是抛异常。
课堂练习(3 题)
"Hello".substring(1, 3)结果是什么?长度为几个字符? 答:"el",长度 2(取下标 1、2,不含 3)。- 把 "192.168.1.1" 按点号切分,代码怎么写?
答:
"192.168.1.1".split("\\.")(正则里 . 要转义)。 - 判断字符串 s 中是否含 "Java",两种写法?
答:
s.contains("Java")或s.indexOf("Java") != -1。
5.3 StringBuilder:需要频繁拼接时用它
String 是"每次改都换新房",StringBuilder 是"在自家房子里改"。
StringBuilder sb = new StringBuilder("Hello");
sb.append(" Java"); // 追加
sb.insert(0, ">> "); // 在指定位置插入
sb.delete(0, 3); // 删除 [0,3)
sb.reverse(); // 反转
String result = sb.toString(); // 最后转回 String
// 典型场景:循环拼接
StringBuilder s = new StringBuilder();
for (int i = 0; i < 1000; i++) {
s.append(i).append(","); // 链式调用
}
System.out.println(s.toString());
| 类 | 可变 | 线程安全 | 适用场景 |
|---|---|---|---|
String | 否 | — | 少量拼接、作为常量 |
StringBuilder | 是 | 否 | 单线程下大量拼接(默认选它,更快) |
StringBuffer | 是 | 是 | 多线程共享时的拼接 |
易错
- StringBuilder 的
delete/insert下标规则和 String 的 substring 一样是左闭右开。 - StringBuilder 重写了 toString,打印它看到的是内容;但 String 的"内容比较"仍是 equals,StringBuilder 之间比较要先 toString 再 equals。
课堂练习(2 题)
- StringBuilder 和 StringBuffer 的主要区别? 答:StringBuilder 非线程安全但更快;StringBuffer 方法加了同步、线程安全但略慢。单线程用 StringBuilder。
- 循环拼接 1 万个数字,为什么用 StringBuilder 比 String 快得多? 答:String 每次 + 都新建对象(1 万个临时对象);StringBuilder 在内部可变数组上追加,只在最后 toString 时生成一个 String。
5.4 格式化输出:printf 与 String.format
输出对齐、保留小数位数,写实验报告必备。
String name = "小明";
int age = 20;
double score = 92.5678;
System.out.printf("姓名:%s,年龄:%d%n", name, age);
System.out.printf("成绩:%.2f%n", score); // 保留 2 位小数 → 92.57
System.out.printf("成绩:%6.2f%n", score); // 宽度 6,右对齐 → " 92.57"
String s = String.format("%s-%d", name, age); // 生成字符串而不打印
System.out.println(s); // 小明-20
| 占位符 | 含义 | 例子 |
|---|---|---|
%d | 整数 | %d → 20 |
%f | 浮点(默认 6 位小数) | %.2f → 92.57 |
%s | 字符串/任意对象 | %s → 小明 |
%c | 字符 | %c → A |
%b | 布尔 | %b → true |
%n | 换行(跨平台,比 \n 推荐) | — |
%% | 输出一个百分号 | %.2f%% → 92.57% |
易错
- 占位符个数要和参数个数一致,类型要匹配,否则抛
IllegalFormatConversionException。 %f默认保留 6 位小数,要 2 位必须写%.2f。
想直接看效果:Java 在线运行环境 粘进去跑。
课堂练习(2 题)
- 用 printf 输出"平均分 87.5 分,及格率 90%",保留 1 位小数怎么写?
答:
System.out.printf("平均分 %.1f 分,及格率 %d%%", 87.5, 90); %d位置传了字符串会怎样? 答:抛 IllegalFormatConversionException(格式化类型不匹配)。