Skip to content

Chapter 12 · 常用类(String / 包装类 / 日期 / Math)

这些类几乎天天用,也几乎天天踩坑。


🎯 本章目标

  • 深入 String:不可变性、字符串常量池、StringBuilder
  • 掌握 包装类:自动装箱拆箱、缓存池
  • 学会现代日期 API:LocalDate / LocalDateTime / Duration(JDK 8+)
  • 用对 Math 类常用方法
  • 知道 RandomOptional 的妙用

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(内容相同)

String 字符串常量池

1.2 String 不可变(Immutable)

java
String s = "hello";
s = s + " world";    // 看似改了 s,实际是新建了一个 String 对象
                     // 老的 "hello" 还在,只是没人引用了

💡 为什么不可变?

  1. 安全(Map 的 key、网络地址)
  2. 线程安全(共享)
  3. 性能(hashCode 可缓存)
  4. 字符串常量池才能存在

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 →   Boolean

2.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(推荐)

新日期 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.0

5. 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.javaString 常用方法、不可变性、性能
WrapperDemo.java包装类、缓存池、装箱拆箱
DateTimeDemo.javaJDK 8+ 日期 API 实战
MathRandomDemo.javaMath + Random
OptionalDemo.javaOptional 优雅处理 null

8. 浏览器演示

打开 demo.html

  • 字符串常量池可视化
  • Integer 缓存池翻车现场
  • 日期格式化在线测试

9. 面试可能会问什么?

Q1: String、StringBuilder、StringBuffer 区别?

对比StringStringBuilderStringBuffer
可变性不可变可变可变
线程安全✅(synchronized)
速度最快中等
适用少量拼接单线程拼接多线程拼接

Q2: 为什么 String 设计为不可变?

  1. 安全:网络/文件路径作为参数,不能被改
  2. 线程安全:天生支持并发
  3. 缓存 hashCode:HashMap 的 key 性能好
  4. 字符串常量池:复用,省内存

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  // false

Q4: 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

  1. month 从 0 开始(反人类)
  2. 可变(线程不安全)
  3. 设计混乱
  4. JDK 8 的新 API 不可变 + 线程安全 + 设计清晰

🎁 本章小结

✅ String 不可变;拼接频繁用 StringBuilder
✅ Integer 缓存池 [-128, 127],包装类比较用 equals
✅ 日期用 LocalDate/LocalDateTime(JDK 8+)
✅ Math 提供基础数学,Random 控制种子
✅ Optional 优雅处理 null(仅用于返回值)

🔗 导航