主题
Chapter 12 · 常用类(String / 包装类 / 日期 / Math)
这些类几乎天天用,也几乎天天踩坑。
🎯 本章目标
- 深入 String:不可变性、字符串常量池、StringBuilder
- 掌握 包装类:自动装箱拆箱、缓存池
- 学会现代日期 API:LocalDate / LocalDateTime / Duration(JDK 8+)
- 用对 Math 类常用方法
- 知道 Random 与 Optional 的妙用
1. String:最熟悉的陌生人
1.1 创建方式
java
String s1 = "hello"; // 字符串常量池
String s2 = "hello"; // 复用 s1 的对象
String s3 = new String("hello"); // 在堆上新建一个对象
System.out.println(s1 == s2); // true(同一个对象)
System.out.println(s1 == s3); // false(不同对象)
System.out.println(s1.equals(s3)); // true(内容相同)1.2 String 不可变(Immutable)
java
String s = "hello";
s = s + " world"; // 看似改了 s,实际是新建了一个 String 对象
// 老的 "hello" 还在,只是没人引用了💡 为什么不可变?
- 安全(Map 的 key、网络地址)
- 线程安全(共享)
- 性能(hashCode 可缓存)
- 字符串常量池才能存在
1.3 String 常用方法
java
String s = "Hello, Java World!";
s.length(); // 18
s.charAt(7); // 'J'
s.indexOf("Java"); // 7
s.contains("Java"); // true
s.startsWith("Hello"); // true
s.endsWith("!"); // true
s.toUpperCase(); // "HELLO, JAVA WORLD!"
s.toLowerCase();
s.trim(); // 去首尾空白
s.strip(); // JDK 11+,包括 Unicode 空白
s.replace("Java", "Python");
s.split(", "); // ["Hello", "Java World!"]
s.substring(7); // "Java World!"
s.substring(7, 11); // "Java"
s.isEmpty(); // false
s.isBlank(); // false(JDK 11+)
String.join("-", "a", "b", "c"); // "a-b-c"
String.format("年龄 %d", 25);1.4 String 拼接性能
java
// ❌ 大循环里这样写性能爆炸
String s = "";
for (int i = 0; i < 10000; i++) {
s += i; // 每次都新建 String
}
// ✅ 用 StringBuilder(单线程)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
String result = sb.toString();| 类 | 线程安全 | 速度 | 用法 |
|---|---|---|---|
String | ✅(不可变) | 慢(频繁拼接) | 少量字符串 |
StringBuilder | ❌ | 最快 | 单线程频繁拼接 |
StringBuffer | ✅(synchronized) | 较慢 | 多线程拼接 |
2. 包装类:让基本类型变成对象
基本类型 包装类
byte → Byte
short → Short
int → Integer ⭐
long → Long
float → Float
double → Double ⭐
char → Character
boolean → Boolean2.1 自动装箱 / 拆箱(JDK 5+)
java
Integer i = 100; // 自动装箱:相当于 Integer.valueOf(100)
int j = i; // 自动拆箱:相当于 i.intValue()
List<Integer> list = new ArrayList<>();
list.add(1); // 装箱:1 → Integer.valueOf(1)
int n = list.get(0); // 拆箱2.2 Integer 缓存池(高频面试坑)
java
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true ❓
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false ❗原因:Integer 缓存了 -128 ~ 127 范围内的对象,超出范围会新建 Integer 对象。
⚠️ 永远用
equals比包装类!
2.3 类型转换工具
java
int n = Integer.parseInt("123");
double d = Double.parseDouble("3.14");
String s1 = Integer.toString(123);
String s2 = String.valueOf(123);
int max = Integer.MAX_VALUE;
int bin = Integer.toBinaryString(42); // "101010"3. 日期时间:用新 API(JDK 8+)
3.1 老 API(不要再用!)
java
Date date = new Date(); // 已过时,month 从 0 开始反人类
Calendar cal = Calendar.getInstance(); // 笨重、可变、非线程安全3.2 新 API(推荐)
| 类 | 用途 |
|---|---|
LocalDate | 只有日期(2024-05-07) |
LocalTime | 只有时间(10:30:45) |
LocalDateTime | 日期 + 时间 |
ZonedDateTime | 带时区的日期时间 |
Instant | 时间戳(机器友好) |
Duration | 时间间隔(基于秒/纳秒) |
Period | 日期间隔(基于年/月/日) |
DateTimeFormatter | 格式化 / 解析 |
3.3 常用操作
java
// 创建
LocalDate today = LocalDate.now();
LocalDate d = LocalDate.of(2024, 5, 7);
LocalDateTime now = LocalDateTime.now();
// 获取字段
today.getYear(); // 2024
today.getMonthValue(); // 5
today.getDayOfWeek(); // TUESDAY
// 加减(不可变,返回新对象)
LocalDate tomorrow = today.plusDays(1);
LocalDate nextMonth = today.plusMonths(1);
LocalDate before = today.minusYears(2);
// 比较
today.isBefore(tomorrow);
today.isAfter(before);
// 格式化
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String s = now.format(fmt);
LocalDateTime parsed = LocalDateTime.parse("2024-05-07 10:30:00", fmt);
// 时间差
Period p = Period.between(LocalDate.of(2000, 1, 1), today);
System.out.println("活了 " + p.getYears() + " 年 " + p.getMonths() + " 月");
Duration d = Duration.between(start, end);
System.out.println("耗时 " + d.toMillis() + " ms");4. Math 类
java
Math.PI; // 3.141592653589793
Math.E; // 2.718281828459045
Math.abs(-7); // 7
Math.max(3, 5);
Math.min(3, 5);
Math.pow(2, 10); // 1024
Math.sqrt(16); // 4.0
Math.cbrt(27); // 3.0 立方根
Math.ceil(3.1); // 4.0 向上取整
Math.floor(3.9); // 3.0 向下取整
Math.round(3.5); // 4 四舍五入
Math.random(); // [0, 1) 随机数
(int)(Math.random() * 100); // [0, 99]
Math.sin(Math.PI / 2);
Math.log(Math.E); // 1.05. Random:更可控的随机数
java
Random r = new Random();
int n1 = r.nextInt(100); // [0, 100)
int n2 = r.nextInt(50, 100); // [50, 100) JDK 17+
double d = r.nextDouble(); // [0, 1)
boolean b = r.nextBoolean();
Random seeded = new Random(42); // 固定种子,结果可重现(测试常用)6. Optional:告别 NPE 噩梦
java
String name = getName();
if (name != null && name.length() > 0) {
System.out.println(name.toUpperCase());
}
// 用 Optional:
Optional.ofNullable(getName())
.filter(s -> !s.isEmpty())
.map(String::toUpperCase)
.ifPresent(System.out::println);
Optional<String> opt = Optional.of("hello");
opt.isPresent(); // true
opt.get(); // "hello"
opt.orElse("default");
opt.orElseThrow();💡 不要把 Optional 作为字段或参数!它是为返回值设计的。
7. 实战练习
| 文件 | 内容 |
|---|---|
StringDemo.java | String 常用方法、不可变性、性能 |
WrapperDemo.java | 包装类、缓存池、装箱拆箱 |
DateTimeDemo.java | JDK 8+ 日期 API 实战 |
MathRandomDemo.java | Math + Random |
OptionalDemo.java | Optional 优雅处理 null |
8. 浏览器演示
打开 demo.html:
- 字符串常量池可视化
- Integer 缓存池翻车现场
- 日期格式化在线测试
9. 面试可能会问什么?
Q1: String、StringBuilder、StringBuffer 区别?
| 对比 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | ✅ | ❌ | ✅(synchronized) |
| 速度 | 慢 | 最快 | 中等 |
| 适用 | 少量拼接 | 单线程拼接 | 多线程拼接 |
Q2: 为什么 String 设计为不可变?
- 安全:网络/文件路径作为参数,不能被改
- 线程安全:天生支持并发
- 缓存 hashCode:HashMap 的 key 性能好
- 字符串常量池:复用,省内存
Q3: String s = "hello" 和 new String("hello") 区别?
"hello":检查常量池有没有,有则复用new String("hello"):在堆上强制创建新对象
java
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
s1 == s2 // true
s1 == s3 // falseQ4: Integer 的 == 比较,什么时候是 true?
java
Integer a = 127, b = 127;
Integer c = 128, d = 128;
a == b // true(缓存池)
c == d // false缓存范围 -128 ~ 127,超出会 new 新对象。永远用 equals 比包装类!
Q5: 怎么把 String 转成 int?
java
int n = Integer.parseInt("123"); // 失败抛 NumberFormatException
int n2 = Integer.valueOf("123"); // 同上,多一步装箱Q6: 为什么不要再用 Date?
- month 从 0 开始(反人类)
- 可变(线程不安全)
- 设计混乱
- JDK 8 的新 API 不可变 + 线程安全 + 设计清晰
🎁 本章小结
✅ String 不可变;拼接频繁用 StringBuilder
✅ Integer 缓存池 [-128, 127],包装类比较用 equals
✅ 日期用 LocalDate/LocalDateTime(JDK 8+)
✅ Math 提供基础数学,Random 控制种子
✅ Optional 优雅处理 null(仅用于返回值)🔗 导航
- ⬅️ 上一章:Chapter 11 · 抽象类与接口
- ➡️ 下一章:Chapter 13 · 异常处理