本章覆盖日常开发最高频的类型:String、基本类型包装类、Java 8 日期时间 API。
1. String 不可变(Immutability)
概念
String 对象创建后内容不可变;任何「修改」都会产生新 String 对象。
原理
- 内部
byte[](JDK 9+ 紧凑表示)与hash等被final保护。 - 字符串常量池(String Pool):字面量
"abc"可能复用同一实例。 - 好处:线程安全、可安全共享、适合作为
Map键;代价:频繁拼接产生垃圾。
完整示例
package com.example.demo;
public class StringImmutableDemo {
public static void main(String[] args) {
String a = "hello";
String b = a;
b = b + " world"; // 新对象,a 仍是 "hello"
System.out.println(a);
System.out.println(b);
// 大量拼接用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append(i).append(',');
}
String joined = sb.toString();
System.out.println(joined);
}
}
练习
循环 1 万次 s += "x" vs StringBuilder
完整例子
package com.example.demo;
public class StringPerfDemo {
public static void main(String[] args) {
long t1 = System.currentTimeMillis();
String s = "";
for (int i = 0; i < 10_000; i++) s += "x";
System.out.println("+= ms=" + (System.currentTimeMillis() - t1));
long t2 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10_000; i++) sb.append('x');
String built = sb.toString();
System.out.println("SB ms=" + (System.currentTimeMillis() - t2) + " len=" + built.length());
}
}
常见坑
- 在循环里
result += line性能差。 - 以为修改了
String字符 — 没有 API 能原地改(charAt只读)。
2. == 与 equals
概念
==:基本类型比值;引用类型比引用地址是否同一对象。equals(Object):逻辑相等,类需正确重写;String已重写为比字符内容。
原理
String s1 = "abc";
String s2 = new String("abc");
String s3 = "abc";
// s1 == s3 可能 true(常量池)
// s1 == s2 false(堆上新对象)
// s1.equals(s2) true
重写 equals 通常也要重写 hashCode(04 章详述)。
完整示例
package com.example.demo;
import java.util.Objects;
public class EqualsDemo {
static class User {
private final Long id;
private String name;
User(Long id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User other)) return false;
return Objects.equals(id, other.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
public static void main(String[] args) {
System.out.println("abc".equals("abc"));
System.out.println(new User(1L, "a").equals(new User(1L, "b")));
}
}
练习
比较 "Java" 与 new String("Java") 的 == 与 equals 结果并解释。
完整例子
package com.example.demo;
public class EqualsExperiment {
public static void main(String[] args) {
String literal = "Java";
String created = new String("Java");
// false:字面量在常量池,new 在堆上是另一对象
System.out.println(literal == created);
// true:equals 比内容
System.out.println(literal.equals(created));
// 常为 true(池化),但业务代码勿用 == 比字符串内容
System.out.println("Java" == "Java");
}
}
常见坑
str.equals(maybeNull)NPE — 写成"常量".equals(str)或Objects.equals(a, b)。- 只重写
equals不重写hashCode,HashMap/HashSet行为异常。
3. 常用 String API
概念
length、isEmpty/isBlank(11+)、substring、indexOf、split、trim/strip、formatted/format。
完整示例
package com.example.demo;
import java.util.Arrays;
public class StringApiDemo {
public static void main(String[] args) {
String csv = " alice , bob , carol ";
String[] parts = Arrays.stream(csv.split(","))
.map(String::strip)
.filter(s -> !s.isEmpty())
.toArray(String[]::new);
System.out.println(Arrays.toString(parts));
String msg = "order %d amount %.2f".formatted(1001, 88.5);
System.out.println(msg);
}
}
练习
给定 "a,b,c,,d",split 后去空串,输出 a|b|c|d(用 StringBuilder)。
完整例子
package com.example.demo;
public class CsvJoinDemo {
public static void main(String[] args) {
String raw = "a,b,c,,d";
StringBuilder sb = new StringBuilder();
for (String part : raw.split(",")) {
if (part.isEmpty()) continue;
if (sb.length() > 0) sb.append('|');
sb.append(part);
}
System.out.println(sb); // a|b|c|d
}
}
常见坑
split(",")参数是正则,.要转义\\.。substring下标越界抛异常。
4. 包装类与自动装箱(Autoboxing)
概念
每个基本类型有包装类:Integer、Long、Double、Boolean…
装箱:基本类型 → 包装类;拆箱:包装 → 基本类型。编译器自动插入 valueOf / xxxValue()。
原理
Integer.valueOf(127)与Integer.valueOf(128)可能池化/cache 行为不同(-128~127默认缓存)。- 包装类可为
null,拆箱时 NPE 或 NPE on unboxing。
完整示例
package com.example.demo;
import java.util.Optional;
public class WrapperDemo {
public static void main(String[] args) {
Integer a = 100; // autobox
int b = a; // unbox
Integer c = null;
// int d = c; // NPE
Optional<Integer> opt = Optional.ofNullable(a);
int safe = opt.orElse(0);
// 比较用 equals,勿用 ==
Integer x = 200, y = 200;
System.out.println(x.equals(y));
}
}
练习
方法 sum(Integer a, Integer b) 处理 null 当 0。
完整例子
package com.example.demo;
public class NullSafeSum {
static int sum(Integer a, Integer b) {
int x = a == null ? 0 : a;
int y = b == null ? 0 : b;
return x + y;
}
public static void main(String[] args) {
System.out.println(sum(3, null)); // 3
System.out.println(sum(null, null)); // 0
}
}
常见坑
Integer i = null; if (i == 1)自动拆箱 NPE。- 用
==比较Integer大数值 — 用equals或基本类型int。
5. LocalDateTime 与 java.time
概念
Java 8+ java.time 替代旧的 Date/Calendar:LocalDate、LocalTime、LocalDateTime、ZonedDateTime、Instant。
原理
不可变、线程安全、清晰 API。持久化/JSON 时常用 ISO-8601 字符串或 epoch 毫秒(与 DB 交互见后续章节)。
完整示例
package com.example.demo;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
public class DateTimeDemo {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalDateTime created = LocalDateTime.of(2026, 7, 18, 21, 30);
LocalDateTime deadline = created.plusDays(7);
long days = ChronoUnit.DAYS.between(today, deadline);
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(created.format(fmt));
System.out.println("days until deadline (signed): " + days);
}
}
练习
输入生日 LocalDate,计算距今天数(Period 或 ChronoUnit)。
完整例子
package com.example.demo;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class BirthdayDays {
public static void main(String[] args) {
LocalDate birthday = LocalDate.of(2000, 1, 1);
LocalDate today = LocalDate.now();
long days = ChronoUnit.DAYS.between(birthday, today);
System.out.println("距今天数: " + days);
}
}
常见坑
LocalDateTime没有时区;跨时区业务用ZonedDateTime或存 UTCInstant。- 旧 API
SimpleDateFormat非线程安全,多线程共享会错;新项目用DateTimeFormatter。
6. Optional(简要)
概念
Optional<T> 显式表示「可能有值」,减少 NPE;勿滥用为字段类型。
完整示例
package com.example.demo;
import java.util.Optional;
public class OptionalDemo {
static Optional<String> findUsername(Long id) {
if (id == null || id <= 0) return Optional.empty();
return Optional.of("user-" + id);
}
public static void main(String[] args) {
findUsername(1L).ifPresent(u -> System.out.println(u));
String name = findUsername(null).orElse("guest");
System.out.println(name);
}
}
常见坑
Optional.get()无值时抛异常 — 用orElse/orElseThrow。- 把
Optional作为 JSON 字段返回给前端 — 通常直接null或省略键。
本章练习(概要)
- 实现
maskEmail("alice@example.com")→a***@example.com(规则自定,文档说明)。 - 解析
"2026-07-18 14:30:00"为LocalDateTime,加 2 小时输出。 - 用
Map<String, Integer>统计句子中单词出现次数(预习 04 章)。
提示见 exercises.md。
完整例子
package com.example.demo.exercise;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
public class Chapter03Exercises {
/** 规则:本地部分保留首字符,其余用 ***,域名不变 */
static String maskEmail(String email) {
int at = email.indexOf('@');
if (at <= 0) return email;
String local = email.substring(0, at);
String domain = email.substring(at);
String masked = local.charAt(0) + "***";
return masked + domain;
}
public static void main(String[] args) {
System.out.println(maskEmail("alice@example.com"));
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dt = LocalDateTime.parse("2026-07-18 14:30:00", fmt).plusHours(2);
System.out.println(dt.format(fmt));
String sentence = "java is fun and java is popular";
Map<String, Integer> freq = new HashMap<>();
for (String w : sentence.toLowerCase().split("\\s+")) {
freq.merge(w, 1, Integer::sum);
}
System.out.println(freq);
}
}
本章小结
| 主题 | 要点 |
|---|---|
| String | 不可变;拼接用 StringBuilder |
| equals | 内容相等;常配合 hashCode |
| 包装类 | 装箱/拆箱;null 拆箱 NPE |
| java.time | LocalDateTime + DateTimeFormatter |