Java · 第 5 章

第 5 章 字符串

String 不可变与字符串常量池;常用方法查表(substring 左闭右开、split 正则陷阱);== 与 equals 的区别;StringBuilder/StringBuffer 对比;printf 与 String.format 格式化。

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 题)
  1. "hello" 用字面量创建和用 new String("hello") 创建,区别在哪? 答:字面量优先复用字符串常量池中的对象(可共享);new 一定在堆上新建对象,不共享。
  2. 为什么 String 不可变还常说"修改了字符串"? 答:实际操作是创建新字符串对象并让变量指向它,原对象内容没变。
  3. 循环里拼接 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)找子串首次出现位置,没有返回 −1s.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 题)
  1. "Hello".substring(1, 3) 结果是什么?长度为几个字符? 答:"el",长度 2(取下标 1、2,不含 3)。
  2. 把 "192.168.1.1" 按点号切分,代码怎么写? 答:"192.168.1.1".split("\\.")(正则里 . 要转义)。
  3. 判断字符串 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 题)
  1. StringBuilder 和 StringBuffer 的主要区别? 答:StringBuilder 非线程安全但更快;StringBuffer 方法加了同步、线程安全但略慢。单线程用 StringBuilder。
  2. 循环拼接 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 题)
  1. 用 printf 输出"平均分 87.5 分,及格率 90%",保留 1 位小数怎么写? 答:System.out.printf("平均分 %.1f 分,及格率 %d%%", 87.5, 90);
  2. %d 位置传了字符串会怎样? 答:抛 IllegalFormatConversionException(格式化类型不匹配)。