Skip to content

第9章:字符串与常用工具类

讲完 OOP,是时候系统地过一遍 Java 标准库里那些你每天都在用的工具了。

📌 代码说明:标注"可复制运行"的代码块包含完整的 main 方法。没标注的是概念示意片段。

你将学会
String·StringBuilder·枚举·时间API
前置知识
第7章 类与对象
预计时间
35 分钟
需要准备
JDK + 编辑器
完成标志
熟练操作字符串和常用工具类

一个故事开头

你写了一个奶茶店收银程序。顾客说:"我要三杯波波奶茶。"

你怎么把这句人话翻译成程序能理解的?你首先得分割字符串("三"、"波波奶茶"),把"三" 转换成数字 3,然后拼接成订单信息。

字符串处理、数字转换、时间记录——这些操作你写每个程序都会遇到的东西,Java 都已经给你准备好了。这一章就是带你把它们过一遍。


1. String——字符串

字符串是不可变的

这是 String 最重要的特性。String 对象一旦创建就不能修改。

java
String s = "Hello";
s.toUpperCase();
System.out.println(s);  // 还是 "Hello"——s 没变!

toUpperCase() 返回了一个字符串,原来的 s 没变过。

java
String s = "Hello";
s = s + " World";         // 不是在原字符串上加——是创建了新字符串 "Hello World"
s = s.substring(0, 5);    // 又创建了新字符串 "Hello"

每次"修改"字符串都在创建新对象。频繁修改时性能差——这时用 StringBuilder(后面会讲)。

常用方法

java
String s = "  Hello, World!  ";

System.out.println(s.length());              // 17
System.out.println(s.trim());                // "Hello, World!"(去首尾空格)
System.out.println(s.strip());               // Java 11+,支持 Unicode 空格
System.out.println(s.toUpperCase());         // "  HELLO, WORLD!  "
System.out.println(s.contains("World"));     // true
System.out.println(s.startsWith("  He"));    // true
System.out.println(s.isEmpty());             // false
System.out.println(s.indexOf("World"));      // 8(World 从索引8开始)
System.out.println(s.indexOf("xyz"));        // -1(没找到)
System.out.println(s.charAt(2));             // 'H'(索引2的字符)
System.out.println(s.substring(2, 7));       // "Hello"(从2到6,不包含7)
System.out.println(s.replace("World", "Java")); // "  Hello, Java!  "

💥 拆了它:== vs equals()

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

System.out.println(a == b);       // true(编译器优化:指向同一个对象)
System.out.println(a == c);       // false!一个在常量池,一个在堆
System.out.println(a.equals(c));  // true(equals 比较内容)

比较字符串内容永远用 equals(),不要用 == == 比较的是引用(地址),不是内容。

StringBuilder——频繁拼接时用

java
// ❌ 大量拼接用 + 号——每次创建新对象,性能差
String s = "";
for (int i = 0; i < 1000; i++) {
    s += i;  // 创建了 1000 个临时 String 对象
}

// ✅ 用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);  // 同一个对象,不创建新字符串
}
String result = sb.toString();

2. Math 和 Random

java
// Math
Math.abs(-5);            // 5(绝对值)
Math.max(10, 20);        // 20
Math.min(10, 20);        // 10
Math.round(3.6);         // 4(四舍五入)
Math.floor(3.6);         // 3.0(向下取整)
Math.ceil(3.2);          // 4.0(向上取整)
Math.pow(2, 10);         // 1024.0(2的10次方)
Math.sqrt(16);           // 4.0(平方根)

// Random
import java.util.Random;
Random rand = new Random();
rand.nextInt(10);         // 0~9 的随机整数
rand.nextInt(10) + 1;     // 1~10 的随机整数
rand.nextDouble();        // 0.0~1.0 的随机小数

3. 包装类与自动装箱

intdoubleboolean 这些基本类型不是对象——但有时候你需要把它们当成对象(比如放进集合)。Java 给每个基本类型配了一个"包装类"。

基本类型包装类
intInteger
doubleDouble
booleanBoolean
charCharacter
longLong
java
// 手动装箱
Integer obj = Integer.valueOf(42);
// 手动拆箱
int val = obj.intValue();

// 自动装箱(Java 5+)——Java 自动帮你转
Integer obj2 = 42;    // 自动装箱:int → Integer
int val2 = obj2;      // 自动拆箱:Integer → int

字符串转数字

java
int num = Integer.parseInt("123");         // "123" → 123
double d = Double.parseDouble("3.14");     // "3.14" → 3.14
boolean b = Boolean.parseBoolean("true");  // "true" → true

💥 拆了它:解析失败的后果

java
int num = Integer.parseInt("奶茶");  // NumberFormatException!

解析失败 Java 报异常——所以实际应用中要包 try-catch(第11章会讲)。


4. 枚举——有限个选项

一年有 12 个月,一周有 7 天,奶茶店杯型有大/中/小——这些"只能从几个固定值里选"的东西,用枚举(enum)。

java
enum Size {
    SMALL, MEDIUM, LARGE
}

enum OrderStatus {
    PENDING, PROCESSING, COMPLETED, CANCELLED
}
java
Size size = Size.MEDIUM;

// switch 枚举
switch (size) {
    case SMALL -> System.out.println("小杯 — 8元");
    case MEDIUM -> System.out.println("中杯 — 12元");
    case LARGE -> System.out.println("大杯 — 15元");
}

// 枚举自带方法
System.out.println(Size.values().length);  // 3(枚举值的个数)
System.out.println(Size.valueOf("SMALL")); // SMALL

枚举比用 int 常量好在哪里? 你不可能传一个 int 值进去,说"杯型是 4"——但用枚举,编译器会检查。


5. 时间和日期

Java 8 之前的时间 API(DateCalendar)设计得很烂。Java 8 引入了全新的 java.time 包——干净、好用。

java
import java.time.*;

// 当前时间
LocalDate today = LocalDate.now();             // 2026-06-22
LocalTime now = LocalTime.now();               // 12:08:00.123
LocalDateTime nowFull = LocalDateTime.now();    // 2026-06-22T12:08:00.123

// 创建具体日期
LocalDate birthday = LocalDate.of(2000, 1, 1);  // 2000-01-01

// 加减
LocalDate tomorrow = today.plusDays(1);
LocalDate nextMonth = today.plusMonths(1);
LocalDate lastYear = today.minusYears(1);

// 比较
today.isAfter(birthday);          // true
today.isBefore(birthday);         // false
today.isEqual(birthday);          // false

// 格式化
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
System.out.println(today.format(fmt));  // 2026年06月22日

6. Record——纯数据载体

如果你有一个类,它的全部作用就是"装着几个值",写起来很啰嗦——要写构造方法、getter、equals、toString...

java
// 传统写法
class Point {
    private int x;
    private int y;
    Point(int x, int y) { this.x = x; this.y = y; }
    public int x() { return x; }
    public int y() { return y; }
    // equals/hashCode/toString...
}

Java 16+ 引入的 Record 帮你把这一切自动化:

java
// Record——一行搞定
record Point(int x, int y) { }

// 直接用
Point p = new Point(3, 5);
System.out.println(p.x());       // 3(自动生成 getter,叫 x() 不是 getX())
System.out.println(p.toString());// Point[x=3, y=5]
System.out.println(p.equals(new Point(3, 5)));  // true(自动生成 equals)

用在奶茶店

java
record Drink(String name, double price) {}

Drink d1 = new Drink("波波奶茶", 15.0);
System.out.println(d1.name());   // 波波奶茶

完整例子

java
import java.time.*;

enum OrderStatus { PENDING, PROCESSING, COMPLETED, CANCELLED }

record OrderItem(String drink, int quantity, double unitPrice) {
    public double total() { return quantity * unitPrice; }
}

public class OrderDemo {
    public static void main(String[] args) {
        // 枚举
        OrderStatus status = OrderStatus.PENDING;
        System.out.println("订单状态: " + status);
        
        // Record
        OrderItem item1 = new OrderItem("波波奶茶", 2, 15.0);
        OrderItem item2 = new OrderItem("柠檬茶", 1, 12.0);
        System.out.println(item1.drink() + " × " + item1.quantity() + " = " + item1.total() + "元");
        
        // 时间
        LocalDateTime orderTime = LocalDateTime.now();
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("HH:mm:ss");
        System.out.println("下单时间: " + orderTime.format(fmt));
        
        // 字符串处理
        String receipt = "订单: " + item1.drink() + "等" + (item1.quantity() + item2.quantity()) + "杯";
        System.out.println(receipt);
        System.out.println("包含'奶茶'? " + receipt.contains("奶茶"));
    }
}

本章小结

  1. String——不可变,比较内容用 equals(),大量拼接用 StringBuilder
  2. Math/Random——数学运算、随机数生成
  3. 包装类——intInteger,字符串转数字用 parseXxx()
  4. 枚举——有限个选项,比 int 常量安全
  5. java.time——LocalDateLocalTimeLocalDateTime,Java 8+ 时间 API
  6. Record——Java 16+,一行搞定纯数据类

✅ 验收标准

完成本章后,你应该能:

  • [ ] 解释 String 不可变性的意义
  • [ ] 区分 equals()== 的字符串比较
  • [ ] 用 StringBuilder 做大量字符串拼接
  • [ ] 用 Math 和 Random 做数学计算和随机数
  • [ ] 声明和使用枚举
  • [ ] 用 java.time API 处理日期时间

📌 常见卡点

  • 用 `==` 比较字符串可能偶然正确但不可靠——总用 `equals()`
  • 字符串拼接太多用 `+`——循环里用 StringBuilder
  • 枚举在 switch 里不用写 break(Java 14+ arrow 语法)
  • 日期格式化注意月份是 1-12(新 API)还是 0-11(旧 Date)

🔜 现在不需要理解

  • 正则表达式——单独章节
  • 国际化(Locale)——项目用到再学
  • DecimalFormat 数字格式化——用到时查
  • ZoneId 时区处理——高级场景


🧪 练习

1. 字符串方法:声明 String s = "Java 编程很有趣",用合适的方法判断它是否包含"编程",提取"编程"这个词,把它替换成"开发"。

2. 枚举:定义一个 Season 枚举(SPRING, SUMMER, AUTUMN, WINTER),写一个 switch 根据季节输出对应的中文名。

3. 时间:用 LocalDate 输出今天的日期、三天后的日期、以及今天是否在你的生日之后。(提示:LocalDate.of(2000, 1, 1)


下一篇

第10章 核心集合

用 ❤️ 构建 | Software Systems Atlas