外观
Java 系统学习文档
适合人群:已经掌握 JavaScript/TypeScript 或其他编程语言,准备学习 Java 后端、Spring 全家桶、企业级服务端开发或 AI 工程后端的学习者。
学习目标:从 Java 语言基础入门,逐步掌握面向对象、集合、异常、泛型、函数式编程、Stream、I/O、并发、JVM、Maven/Gradle、测试和企业级开发基础。
核对时间:2026-06-25。Oracle 官方当前显示 JDK 26 是最新 release,JDK 25 是最新 LTS。学习和生产项目应优先选择 LTS 版本或团队规定版本。
官方资料:
- Java SE Documentation:https://docs.oracle.com/en/java/javase/
- JDK 26 Documentation:https://docs.oracle.com/en/java/javase/26/
- JDK 25 Documentation:https://docs.oracle.com/en/java/javase/25/
- Oracle Java Downloads:https://www.oracle.com/java/technologies/downloads/
- OpenJDK:https://openjdk.org/
目录
第一篇:Java 基础
- Java 是什么
- JDK、JRE、JVM
- 安装与第一个程序
- 基本语法
- 数据类型和变量
- 运算符和流程控制
第二篇:面向对象
- 类和对象
- 封装、继承、多态
- 接口和抽象类
- static、final、访问修饰符
- record、enum、sealed class
第三篇:常用语言能力
- String 和常用类
- 异常处理
- 泛型
- 集合框架
- Lambda 和函数式接口
- Stream API
- Optional
第四篇:工程与后端基础
- 包、模块和项目结构
- Maven 和 Gradle
- 单元测试
- I/O 和 NIO
- 日期时间 API
- JSON 处理
第五篇:并发、JVM 与进阶
- 线程和线程池
- synchronized、Lock、volatile
- CompletableFuture
- JVM 内存模型
- 垃圾回收
- 性能排查
- Java 与 Spring 学习路线
第一篇:Java 基础
1. Java 是什么
Java 是一门面向对象、强类型、跨平台的编程语言,广泛用于企业后端、Android、大数据、金融系统、中间件和云原生服务。
Java 的核心特点:
- 编译成字节码,在 JVM 上运行。
- 强类型,编译期能发现大量类型错误。
- 面向对象能力成熟。
- 标准库和企业生态丰富。
- Spring 生态在企业后端非常主流。
- 长期稳定,适合大型工程。
前端开发者学习 Java 最大的变化:
- 从动态类型转到静态类型。
- 从函数和对象混合风格转到类和接口体系。
- 从 npm 生态转到 Maven/Gradle 生态。
- 从 Node.js 单线程事件循环转到 JVM 多线程模型。
2. JDK、JRE、JVM
三者关系:
text
JDK = 开发工具包
├── javac 编译器
├── java 命令
├── 标准库
└── JVM
JRE = 运行环境
├── 标准库
└── JVM
JVM = Java 虚拟机,执行字节码现代开发通常直接安装 JDK。
流程:
text
Hello.java
-> javac 编译
-> Hello.class 字节码
-> java 运行在 JVM 上Java 的跨平台来自 JVM。不同系统安装不同 JVM,但运行同一份字节码。
3. 安装与第一个程序
查看版本:
bash
java --version
javac --version第一个程序:
java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello Java");
}
}编译和运行:
bash
javac Hello.java
java Hello解释:
- 文件名要和 public class 名一致。
main是程序入口。- Java 语句以分号结尾。
- Java 对大小写敏感。
4. 基本语法
变量:
java
int age = 18;
String name = "Alice";
boolean active = true;方法:
java
public static int add(int a, int b) {
return a + b;
}类:
java
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
}Java 比 JavaScript 更强调类型声明和类结构。学习初期不要嫌啰嗦,这些约束正是大型项目可维护性的来源。
5. 数据类型和变量
基本类型:
| 类型 | 含义 |
|---|---|
| byte | 8 位整数 |
| short | 16 位整数 |
| int | 32 位整数 |
| long | 64 位整数 |
| float | 32 位浮点 |
| double | 64 位浮点 |
| char | 字符 |
| boolean | true/false |
引用类型:
java
String name = "Alice";
User user = new User("Alice");区别:
- 基本类型保存值。
- 引用类型保存对象引用。
String不是基本类型。
金额不要用 double,应使用 BigDecimal。
java
BigDecimal price = new BigDecimal("19.99");6. 运算符和流程控制
if:
java
if (age >= 18) {
System.out.println("adult");
} else {
System.out.println("minor");
}switch:
java
switch (role) {
case "ADMIN" -> System.out.println("admin");
case "USER" -> System.out.println("user");
default -> System.out.println("unknown");
}for:
java
for (int i = 0; i < 10; i++) {
System.out.println(i);
}增强 for:
java
for (String item : items) {
System.out.println(item);
}第二篇:面向对象
7. 类和对象
类是模板,对象是实例。
java
public class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
}创建对象:
java
User user = new User(1L, "Alice");Java 后端开发会大量使用类表达:
- Controller
- Service
- Repository
- DTO
- Entity
- Configuration
8. 封装、继承、多态
封装:隐藏内部状态,通过方法访问。
java
public class Account {
private BigDecimal balance;
public void deposit(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("amount must be positive");
}
balance = balance.add(amount);
}
}继承:
java
public class AdminUser extends User {
}多态:
java
List<User> users = List.of(new AdminUser(), new NormalUser());实践建议:
- 优先组合,谨慎继承。
- 继承适合稳定的 is-a 关系。
- 业务扩展更多使用接口和组合。
9. 接口和抽象类
接口定义能力:
java
public interface PaymentService {
PaymentResult pay(PaymentRequest request);
}实现:
java
public class AlipayService implements PaymentService {
@Override
public PaymentResult pay(PaymentRequest request) {
return new PaymentResult(true);
}
}抽象类:
java
public abstract class BaseService {
protected void log(String message) {
System.out.println(message);
}
}选择:
- 只定义能力和契约:接口。
- 需要共享部分实现和状态:抽象类。
Spring 中接口非常重要,因为依赖注入常以接口定义边界。
10. static、final、访问修饰符
访问修饰符:
public:所有地方可见。protected:同包或子类可见。- 默认:同包可见。
private:类内部可见。
static 表示属于类,而不是对象:
java
public static final String DEFAULT_ROLE = "USER";final:
- 修饰变量:不能重新赋值。
- 修饰方法:不能被重写。
- 修饰类:不能被继承。
常量通常:
java
public static final int PAGE_SIZE = 20;11. record、enum、sealed class
record 适合不可变数据载体:
java
public record UserDto(Long id, String name) {
}enum:
java
public enum OrderStatus {
PENDING,
PAID,
CANCELLED
}sealed class 限制继承范围:
java
public sealed interface Result permits Success, Failure {
}这些现代 Java 特性能让代码更简洁,尤其适合 DTO、状态和结果类型。
第三篇:常用语言能力
12. String 和常用类
String 不可变:
java
String a = "hello";
String b = a.toUpperCase();大量拼接用:
java
StringBuilder builder = new StringBuilder();
builder.append("Hello");
builder.append(" Java");常用类:
BigDecimalLocalDateTimeOptionalUUIDObjectsCollectionsListMap
13. 异常处理
java
try {
service.process();
} catch (BusinessException e) {
log.error("业务异常", e);
} finally {
cleanup();
}异常类型:
- Checked Exception:编译期要求处理。
- Runtime Exception:运行时异常。
- Error:严重错误,通常不捕获。
业务开发中常见:
java
throw new IllegalArgumentException("参数错误");Spring 项目中通常使用全局异常处理,把异常转换为统一 API 响应。
14. 泛型
泛型让类和方法支持类型参数。
java
List<String> names = new ArrayList<>();泛型方法:
java
public static <T> T first(List<T> list) {
return list.get(0);
}通配符:
java
List<? extends Number> numbers;
List<? super Integer> integers;理解:
extends适合读取。super适合写入。
泛型是 Java 集合、Repository、API 响应封装的基础。
15. 集合框架
常用接口:
List:有序,可重复。Set:不重复。Map:键值对。Queue:队列。
常用实现:
ArrayListLinkedListHashSetTreeSetHashMapConcurrentHashMap
示例:
java
Map<Long, User> userMap = users.stream()
.collect(Collectors.toMap(User::id, user -> user));选择:
- 按下标访问多:ArrayList。
- 去重:HashSet。
- 键值查找:HashMap。
- 并发安全 Map:ConcurrentHashMap。
16. Lambda 和函数式接口
Lambda:
java
users.forEach(user -> System.out.println(user.name()));函数式接口:
java
@FunctionalInterface
public interface Validator<T> {
boolean validate(T value);
}常用内置接口:
Function<T, R>Consumer<T>Supplier<T>Predicate<T>
前端开发者可以把 Lambda 类比 JavaScript 箭头函数,但 Java 的 Lambda 有明确类型约束。
17. Stream API
Stream 用于集合的声明式处理。
java
List<String> names = users.stream()
.filter(User::active)
.map(User::name)
.toList();常用操作:
filtermapflatMapsorteddistinctlimitcollectgroupingBy
分组:
java
Map<OrderStatus, List<Order>> grouped = orders.stream()
.collect(Collectors.groupingBy(Order::status));注意:
- Stream 不要写过度复杂。
- 有副作用的操作要谨慎。
- 数据量巨大时不要盲目 parallelStream。
18. Optional
Optional 表示可能没有值。
java
Optional<User> user = userRepository.findById(id);使用:
java
User user = userRepository.findById(id)
.orElseThrow(() -> new NotFoundException("用户不存在"));避免:
java
optional.get()除非你已经确定有值。Optional 的目的是减少空指针,而不是换一种方式制造异常。
第四篇:工程与后端基础
19. 包、模块和项目结构
包名通常使用反向域名:
java
com.example.demoSpring 项目常见结构:
text
src/main/java/com/example/app/
├── controller/
├── service/
├── repository/
├── entity/
├── dto/
├── config/
└── exception/前端类比:
text
controller 类似 API 路由入口
service 类似业务逻辑层
repository 类似数据库访问层
dto 类似接口请求/响应类型20. Maven 和 Gradle
Maven 使用 pom.xml:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>常用命令:
bash
mvn clean test
mvn clean packageGradle 使用 build.gradle 或 build.gradle.kts:
kotlin
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
}常用命令:
bash
./gradlew test
./gradlew build前端类比:
text
Maven/Gradle 类似 npm/pnpm + 构建脚本 + 依赖管理21. 单元测试
JUnit:
java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@Test
void addShouldReturnSum() {
assertEquals(3, Calculator.add(1, 2));
}
}Mockito:
java
when(userRepository.findById(1L)).thenReturn(Optional.of(user));测试分层:
- 单元测试:测试单个类或方法。
- 集成测试:测试多个组件协作。
- E2E 测试:测试完整请求链路。
Spring 项目中测试非常重要,因为业务逻辑通常集中在 Service 层。
22. I/O 和 NIO
传统 I/O:
java
String content = Files.readString(Path.of("a.txt"));
Files.writeString(Path.of("b.txt"), content);流:
java
try (InputStream input = Files.newInputStream(path)) {
byte[] bytes = input.readAllBytes();
}try-with-resources 会自动关闭资源。
NIO 提供:
- Path
- Files
- Channel
- Buffer
- Selector
普通业务项目先掌握 java.nio.file.Files 即可。
23. 日期时间 API
使用 java.time:
java
LocalDate date = LocalDate.now();
LocalDateTime time = LocalDateTime.now();
Instant instant = Instant.now();时区:
java
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));建议:
- 数据库存储统一时间标准。
- API 明确时间格式。
- 避免使用旧的
Date和Calendar作为新项目主要时间 API。
24. JSON 处理
Java 后端常用 Jackson。
对象转 JSON:
java
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);JSON 转对象:
java
User user = mapper.readValue(json, User.class);Spring Boot 默认集成 Jackson,Controller 返回对象时通常自动序列化成 JSON。
第五篇:并发、JVM 与进阶
25. 线程和线程池
创建线程:
java
Thread thread = new Thread(() -> System.out.println("run"));
thread.start();线程池:
java
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> service.process());生产中不要随意无限创建线程,应使用线程池。
线程池关注:
- 核心线程数
- 最大线程数
- 队列
- 拒绝策略
- 任务耗时
26. synchronized、Lock、volatile
synchronized:
java
public synchronized void increment() {
count++;
}Lock:
java
lock.lock();
try {
count++;
} finally {
lock.unlock();
}volatile 保证可见性,但不保证复合操作原子性。
java
private volatile boolean running = true;并发学习重点:
- 原子性
- 可见性
- 有序性
- 线程安全
- 锁竞争
- 死锁
27. CompletableFuture
异步任务:
java
CompletableFuture<User> userFuture =
CompletableFuture.supplyAsync(() -> userService.getUser(id));组合:
java
CompletableFuture<Result> result =
userFuture.thenCombine(orderFuture, Result::new);适合:
- 并行调用多个外部接口。
- 异步组合结果。
- 非阻塞业务编排。
注意:
- 要指定合适线程池。
- 要处理异常。
- 不要让异步逻辑失控。
28. JVM 内存模型
JVM 运行时内存大致包括:
- 堆:对象主要存放位置。
- 栈:方法调用帧和局部变量。
- 方法区/元空间:类元数据。
- 程序计数器。
- 本地方法栈。
常见问题:
- 堆内存溢出。
- 栈溢出。
- 类加载过多。
- 内存泄漏。
前端类比:浏览器也有 JS 堆和垃圾回收,但 JVM 的可调参数、线程模型和服务端长生命周期让排查更复杂。
29. 垃圾回收
垃圾回收负责回收不再使用的对象。
学习重点:
- 对象是否可达。
- Young/Old 分代思想。
- Minor GC 和 Full GC。
- 常见 GC 日志。
- 常见收集器。
业务开发不必一开始深入所有 GC 算法,但要知道:
- 大量临时对象会增加 GC 压力。
- 内存泄漏会导致频繁 GC 和 OOM。
- 生产问题要会看日志和指标。
30. 性能排查
常见工具:
jpsjstackjmapjcmd- Java Flight Recorder
- VisualVM
- Arthas
排查方向:
- CPU 高:看线程栈。
- 内存高:看堆 dump。
- 接口慢:看数据库、外部调用、锁和线程池。
- GC 频繁:看对象分配和内存泄漏。
31. Java 与 Spring 学习路线
Java 学习优先级:
- 基础语法和面向对象。
- 集合、泛型、异常。
- Lambda、Stream、Optional。
- Maven/Gradle 和测试。
- I/O、JSON、日期时间。
- 并发基础。
- JVM 基础。
- Spring Boot。
- Spring MVC、Spring Data JPA、Spring Security。
- Spring Cloud、消息队列、缓存和微服务。
如果你的目标是偏前端 AI 全栈,不需要一开始成为 Java 专家,但要能读懂企业后端、理解 Spring 项目结构和 API 开发方式。
32. 核心知识点深度补全
本章补全 Java 学习中最容易被写成“名词解释”的部分:集合、泛型、Stream、异常、并发、JVM。目标不是背 API,而是能在企业项目中选择正确的数据结构、写出可靠的并发代码,并能理解线上排查时看到的 JVM 指标。
32.1 集合框架全景
Java 集合框架主要分为两条线:
text
Collection
├── List:有序、可重复
│ ├── ArrayList
│ └── LinkedList
├── Set:不重复
│ ├── HashSet
│ ├── LinkedHashSet
│ └── TreeSet
└── Queue / Deque:队列、双端队列
├── ArrayDeque
└── PriorityQueue
Map:键值对,不继承 Collection
├── HashMap
├── LinkedHashMap
├── TreeMap
└── ConcurrentHashMap选择表:
| 需求 | 推荐 |
|---|---|
| 按下标快速访问 | ArrayList |
| 频繁头尾插入删除 | ArrayDeque,不要第一反应用 LinkedList |
| 去重且不关心顺序 | HashSet |
| 去重且保持插入顺序 | LinkedHashSet |
| 去重且排序 | TreeSet |
| key-value 快速查找 | HashMap |
| key-value 且保持插入顺序 | LinkedHashMap |
| key-value 且按 key 排序 | TreeMap |
| 并发读写 Map | ConcurrentHashMap |
32.2 ArrayList、LinkedList、ArrayDeque
ArrayList 底层是动态数组:
- 按下标读取快。
- 尾部追加通常快。
- 中间插入/删除需要移动元素。
- 扩容会创建更大的数组并复制元素。
java
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
System.out.println(names.get(0));LinkedList 是双向链表,但现代 Java 开发中不要因为“插入删除多”就盲目使用它:
- 链表节点有额外对象开销。
- CPU 缓存局部性差。
- 按下标访问需要遍历。
- 很多队列场景
ArrayDeque更合适。
队列推荐:
java
Deque<String> queue = new ArrayDeque<>();
queue.addLast("task1");
queue.addLast("task2");
System.out.println(queue.removeFirst());32.3 HashMap 的核心机制
HashMap 通过 hash 定位桶,再在桶中查找 key。
简化过程:
- 调用 key 的
hashCode()得到 hash。 - 根据数组长度计算桶下标。
- 如果桶为空,直接放入。
- 如果桶不为空,使用
equals()判断是否同一个 key。 - 如果 key 相同,覆盖 value;否则追加到链表或红黑树结构中。
java
Map<Long, String> userNames = new HashMap<>();
userNames.put(1L, "Alice");
userNames.put(2L, "Bob");
System.out.println(userNames.get(1L));equals 和 hashCode 必须一致:
java
public final class UserKey {
private final Long tenantId;
private final Long userId;
public UserKey(Long tenantId, Long userId) {
this.tenantId = tenantId;
this.userId = userId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof UserKey other)) return false;
return Objects.equals(tenantId, other.tenantId)
&& Objects.equals(userId, other.userId);
}
@Override
public int hashCode() {
return Objects.hash(tenantId, userId);
}
}规则:
- 两个对象
equals为 true,则hashCode必须相同。 - 两个对象
hashCode相同,equals不一定为 true。 - 作为 Map key 的对象应尽量不可变,否则放入后修改字段可能导致无法取回。
危险示例:
java
Map<List<String>, String> map = new HashMap<>();
List<String> key = new ArrayList<>(List.of("a"));
map.put(key, "value");
key.add("b");
System.out.println(map.get(key)); // 可能取不到32.4 HashSet 为什么依赖 HashMap
HashSet 本质上可以理解为只关心 key 的 HashMap。
java
Set<String> roles = new HashSet<>();
roles.add("admin");
roles.add("admin");
System.out.println(roles.size()); // 1如果自定义对象放入 HashSet,也必须正确实现 equals 和 hashCode。
java
Set<UserKey> keys = new HashSet<>();
keys.add(new UserKey(1L, 100L));
keys.add(new UserKey(1L, 100L));
System.out.println(keys.size()); // equals/hashCode 正确时是 132.5 泛型与类型擦除
泛型让编译器帮助检查类型:
java
List<String> names = new ArrayList<>();
names.add("Alice");
// names.add(123); 编译失败但 Java 泛型主要在编译期生效,运行时会发生类型擦除:
java
List<String> names = new ArrayList<>();
List<Integer> numbers = new ArrayList<>();
System.out.println(names.getClass() == numbers.getClass()); // true类型擦除带来的限制:
- 不能
new T()。 - 不能直接创建泛型数组
new List<String>[10]。 - 运行时无法直接知道
List<String>的String。 - 方法重载不能只靠泛型参数区分。
java
// 编译问题示例:擦除后签名冲突
// void handle(List<String> names) {}
// void handle(List<Integer> numbers) {}32.6 通配符与 PECS
通配符用于表达“某类泛型的范围”。
java
List<? extends Number> numbers;
List<? super Integer> integers;PECS 原则:
text
Producer Extends, Consumer Super如果一个集合主要“生产”数据给你读,用 extends:
java
double sum(List<? extends Number> numbers) {
double total = 0;
for (Number number : numbers) {
total += number.doubleValue();
}
return total;
}如果一个集合主要“消费”你写入的数据,用 super:
java
void addDefaultIds(List<? super Integer> target) {
target.add(1);
target.add(2);
}理解:
? extends Number:我知道取出来至少是Number,但不安全写入具体子类。? super Integer:我知道可以写入Integer,但取出来只能当Object。
32.7 Stream API 的执行模型
Stream 不是集合,它是一条数据处理流水线。
java
List<String> result = users.stream()
.filter(User::active)
.map(User::name)
.distinct()
.sorted()
.toList();特点:
- 中间操作是惰性的:
filter、map、distinct不会立即执行。 - 终止操作触发执行:
toList、collect、forEach、count。 - Stream 通常只能消费一次。
java
Stream<String> stream = Stream.of("a", "b", "c");
stream.count();
// stream.toList(); 再次使用会抛异常调试 Stream 时可以用 peek,但不要把业务副作用塞进 peek:
java
List<String> names = users.stream()
.filter(User::active)
.peek(user -> log.debug("active user {}", user.id()))
.map(User::name)
.toList();parallelStream 不等于自动变快:
- 数据量小可能更慢。
- 有共享状态会出现线程安全问题。
- 默认使用公共 ForkJoinPool,可能影响其他并行任务。
- I/O 密集任务不一定适合 parallelStream。
32.8 异常处理的工程分层
Java 异常分为:
| 类型 | 说明 |
|---|---|
| Checked Exception | 编译器要求处理或声明,例如 IOException |
| Runtime Exception | 运行时异常,例如 NullPointerException、IllegalArgumentException |
| Error | JVM 或系统级严重问题,一般不捕获 |
业务项目中常见分层:
text
Controller
-> 参数校验失败:返回 400
Service
-> 业务规则失败:抛业务异常
Repository
-> 数据访问失败:转换为统一数据异常或向上抛
GlobalExceptionHandler
-> 统一转换 API 响应示例:
java
public class BusinessException extends RuntimeException {
private final String code;
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
public String code() {
return code;
}
}原则:
- 不要吞异常。
- 不要用异常做普通流程控制。
- 不要在底层随意返回
null表示失败。 - 异常日志要包含上下文,但不要泄露敏感数据。
- API 层统一处理异常,不要每个 Controller 手写一遍。
32.9 线程、线程池与任务队列
直接创建线程:
java
Thread thread = new Thread(() -> {
System.out.println("work");
});
thread.start();生产项目更常用线程池:
java
ExecutorService executor = Executors.newFixedThreadPool(8);
Future<String> future = executor.submit(() -> {
return "result";
});
System.out.println(future.get());
executor.shutdown();更推荐显式配置 ThreadPoolExecutor:
java
ThreadPoolExecutor executor = new ThreadPoolExecutor(
8,
16,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000),
new ThreadFactory() {
private final AtomicInteger index = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "biz-worker-" + index.incrementAndGet());
}
},
new ThreadPoolExecutor.CallerRunsPolicy()
);参数含义:
| 参数 | 作用 |
|---|---|
| corePoolSize | 核心线程数 |
| maximumPoolSize | 最大线程数 |
| keepAliveTime | 非核心线程空闲存活时间 |
| workQueue | 等待队列 |
| threadFactory | 线程命名、优先级、异常处理 |
| rejectedExecutionHandler | 队列满且线程到上限后的拒绝策略 |
拒绝策略:
AbortPolicy:直接抛异常。CallerRunsPolicy:由提交任务的线程执行,形成反压。DiscardPolicy:静默丢弃,不推荐用于关键任务。DiscardOldestPolicy:丢弃最老任务,风险较高。
32.10 synchronized、volatile、Lock
synchronized 保证互斥和可见性:
java
public class Counter {
private int value;
public synchronized void increment() {
value++;
}
public synchronized int get() {
return value;
}
}volatile 保证可见性和一定的有序性,但不保证复合操作原子性:
java
private volatile boolean running = true;
public void stop() {
running = false;
}
public void run() {
while (running) {
// do work
}
}下面不是线程安全:
java
private volatile int count = 0;
public void increment() {
count++; // 读、加、写三步,不是原子操作
}可以使用 AtomicInteger:
java
private final AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}Lock 提供更灵活的锁能力:
java
private final ReentrantLock lock = new ReentrantLock();
public void update() {
lock.lock();
try {
// critical section
} finally {
lock.unlock();
}
}32.11 CompletableFuture 与异步编排
CompletableFuture 适合组合多个异步任务:
java
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(
() -> userService.getUser(userId),
executor
);
CompletableFuture<List<Order>> orderFuture = CompletableFuture.supplyAsync(
() -> orderService.listOrders(userId),
executor
);
CompletableFuture<Dashboard> dashboardFuture = userFuture.thenCombine(
orderFuture,
Dashboard::new
);
Dashboard dashboard = dashboardFuture.join();异常处理:
java
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> remoteCall(), executor)
.exceptionally(error -> {
log.error("remote call failed", error);
return "fallback";
});注意:
- 生产中不要默认使用公共线程池,显式传入业务线程池。
- 异步不是越多越好,外部依赖也有容量上限。
join()会包装异常为CompletionException。- 要设计超时、降级和取消策略。
32.12 虚拟线程
现代 Java 已经提供虚拟线程能力,适合大量阻塞式 I/O 场景。它让“一请求一线程”的模型在很多服务端场景重新变得可行。
java
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<String> future = executor.submit(() -> {
return blockingHttpCall();
});
System.out.println(future.get());
}适合:
- 大量网络 I/O。
- 传统阻塞式 JDBC、HTTP 调用。
- 想降低异步回调复杂度的服务端代码。
不适合:
- CPU 密集计算。
- 持有重量级锁长时间阻塞。
- 没有限流的外部依赖调用。
32.13 JVM 运行时内存
常见区域:
| 区域 | 作用 |
|---|---|
| 程序计数器 | 当前线程执行位置 |
| Java 虚拟机栈 | 方法调用栈、局部变量表、操作数栈 |
| 本地方法栈 | Native 方法调用 |
| 堆 | 对象实例主要分配区域 |
| 方法区/元空间 | 类元数据、常量、方法信息等 |
| 直接内存 | NIO、Netty 等可能使用 JVM 堆外内存 |
常见问题:
StackOverflowError:递归过深或栈帧过多。OutOfMemoryError: Java heap space:堆空间不足。OutOfMemoryError: Metaspace:类元数据过多,常见于动态生成类或类加载泄漏。- 直接内存泄漏:Netty、NIO、文件传输等场景需要关注。
32.14 Java 内存模型与 happens-before
多线程问题的核心是:
- 原子性:操作是否不可分割。
- 可见性:一个线程的修改另一个线程何时可见。
- 有序性:编译器和 CPU 可能重排序。
happens-before 是 Java 判断可见性和顺序的规则集合。常见规则:
- 一个线程内,前面的操作 happens-before 后面的操作。
- 对同一个锁的 unlock happens-before 后续 lock。
- 对 volatile 变量的写 happens-before 后续读。
- 线程
start()happens-before 线程内部动作。 - 线程内所有动作 happens-before 其他线程成功从
join()返回。
这就是为什么 synchronized 和 volatile 不只是“加锁/标记”,还提供内存可见性语义。
32.15 GC 与线上排查
GC 关注三件事:
- 哪些对象还活着。
- 如何回收不活跃对象。
- 如何减少停顿和吞吐损失。
排查思路:
text
接口变慢
-> 看应用日志和链路追踪
-> 看 CPU、内存、GC 指标
-> CPU 高:jstack / async-profiler / Arthas
-> 内存涨:jmap / heap dump / MAT
-> GC 频繁:GC 日志、对象分配、缓存策略常用命令:
bash
jps -l
jstack <pid> > thread.txt
jmap -histo <pid> | head
jcmd <pid> GC.heap_info学习重点不是背所有 GC 算法,而是知道:
- 大量短生命周期对象会增加 Young GC 压力。
- 长生命周期缓存如果不设置上限,会造成 Old 区压力。
- 线程池、连接池、缓存、静态集合都可能持有对象导致泄漏。
- 日志、监控、heap dump、线程栈要结合看。
33. 现代 Java 特性与常用类深度补全
本章补齐前 32 章点到为止、但日常开发高频出现的语言特性与常用类:模式匹配、record/sealed、文本块、var、String 内部机制、装箱缓存、BigDecimal、Optional 与日期时间。目标是写出更安全、更简洁的现代 Java 代码,并理解这些特性背后的语义。
33.1 模式匹配:instanceof 与 switch
Java 16 正式引入 instanceof 模式匹配(JEP 394),避免「先判断类型再强转」的样板代码:
java
// 传统写法
if (obj instanceof User) {
User user = (User) obj;
System.out.println(user.name());
}
// 模式匹配写法
if (obj instanceof User user) {
System.out.println(user.name());
}user 是模式变量,只有当 obj 确实是 User 时才会被绑定,作用域限定在条件成立的代码块内。
Java 21 正式引入 switch 模式匹配(JEP 441),把「类型判断 + 解构 + 分支」写成一条表达式:
java
String describe(Object obj) {
return switch (obj) {
case null -> "空值";
case Integer i -> "整数 " + i;
case String s when s.isBlank() -> "空白字符串";
case String s -> "字符串 " + s;
case User u -> "用户 " + u.name();
default -> "其他类型";
};
}要点:
case分支可以带when守卫(guard)做额外条件。switch作为表达式时必须穷尽(exhaustive)或提供default。- 匹配顺序从上到下,先匹配更具体的分支。
- 相比
if-else链,模式匹配让「按类型分派」更集中、更不容易漏分支。
💡 前端开发者可以把它类比 TypeScript 的「可辨识联合 + 类型收窄」:instanceof 类似 typeof/in 收窄,switch 模式匹配类似对 union 类型做穷尽处理。
33.2 record 深入:紧凑构造器与序列化
record(Java 16 转正)是「不可变数据载体」的简洁写法,编译器自动生成:
- 私有 final 字段。
- 全参构造器。
- 访问器方法(
name()而非getName())。 equals()/hashCode()/toString()。
record 提供「紧凑构造器」(compact constructor),用于在赋值前做参数校验或规范化:
java
public record User(Long id, String name) {
public User {
if (id == null || id <= 0) {
throw new IllegalArgumentException("id 必须为正数");
}
name = name == null ? "" : name.trim();
}
}紧凑构造器里不需要写 this.id = id,编译器会自动完成字段赋值;你只需要写校验逻辑。
⚠️ record 的 equals/hashCode 基于所有字段值,且字段不可变,因此适合作为 Map 的 key、DTO、值对象。但它不适合:
- 需要可变状态的实体(如 JPA 实体,JPA 通常需要无参构造器和可变字段)。
- 字段很多、需要精细控制 equals 语义的对象。
record 可以被序列化,但字段是 final 的;反序列化通过构造器而不是反射注入,这点和普通类不同(更安全)。
33.3 sealed class / sealed interface 深入
sealed(Java 17 转正)用于限制「谁能继承/实现」,把继承体系从「开放」变成「受控枚举」:
java
public sealed interface Result
permits Success, Failure, Pending {}
public record Success(String value) implements Result {}
public record Failure(String reason) implements Result {}
public record Pending() implements Result {}价值:
- 编译器知道
Result只有三种实现,switch模式匹配可以穷尽,无需default。 - 防止业务中随意新增一个实现却漏掉所有分支处理。
- 和 record 组合后,天然适合表达「代数数据类型 / 结果类型」。
规则:
permits列出的子类必须在同一模块或包内(或显式声明为non-sealed开放给外部扩展)。- 子类可以是
final(不再被继承)、sealed(继续受控)或non-sealed(重新开放继承)。
💡 这是 Java 版的「可辨识联合」,配合 33.1 的模式匹配,可以像处理 TS union 一样安全地处理业务状态。
33.4 文本块 Text Blocks
文本块(Java 15 转正)用三个双引号表示多行字符串,自动处理换行和缩进:
java
String sql = """
SELECT id, name
FROM users
WHERE active = true
""";规则:
- 首行换行会被忽略(紧跟
"""后的第一个换行不算内容)。 - 「附带缩进」(incidental indentation)是公共的最小缩进,会被去掉。
- 内部的双引号一般无需转义,但
"""序列需要处理。
常用技巧:行尾加 \ 表示该行不换行;\s 表示保留末尾空格。
⚠️ 文本块非常适合嵌入 SQL、JSON、HTML、Prompt 模板,但要注意它仍会被编译成普通字符串,避免在块内混入不一致的缩进导致内容出现多余空格。
33.5 var 局部变量类型推断
var(Java 10 引入)让编译器推断局部变量的类型:
java
var users = new ArrayList<User>(); // ArrayList<User>
var name = "Alice"; // String
var count = 10; // int要点:
- 只能用于「带初始化器」的局部变量,不能用于字段、方法参数或返回值(这些位置仍要显式类型)。
- 类型在编译期就确定,运行时没有任何动态性 —— Java 仍然是强静态类型。
- 初始化器为
null时不能推断。
工程建议:当类型在右侧已经显而易见(new ArrayList<>()、Map.of(...))时用 var 减少重复;当类型语义重要、右侧不直观时保留显式类型。团队应统一风格。
33.6 String 深入:不可变、常量池与 intern
第 12 章提到 String 不可变,这里补内部机制。
String 不可变的原因:
- 安全:作为类加载器、网络地址、文件路径、Map key 的载体,不可变避免被意外篡改。
- 缓存:
hashCode()可以缓存,作为 HashMap key 时性能稳定。 - 线程安全:多线程共享无需加锁。
- 常量池:相同字面量可以共享同一份对象。
字符串常量池(String Pool)位于堆中,String 字面量会放入常量池:
java
String a = "hello";
String b = "hello";
System.out.println(a == b); // true,指向常量池同一对象而 new String("hello") 会新建堆对象,不指向常量池:
java
String c = new String("hello");
System.out.println(a == c); // falseintern() 可以把字符串放入常量池并返回池中引用:
java
String d = new String("hello").intern();
System.out.println(a == d); // true⚠️ 判断字符串内容是否相等永远用 equals(),不要用 ==(== 比较的是引用)。intern() 在现代 Java 中一般不需要手动调用,滥用反而可能给常量池带来压力。
33.7 装箱/拆箱与 Integer 缓存
基本类型和包装类型之间的自动转换叫装箱(boxing)/拆箱(unboxing):
java
Integer boxed = 42; // 装箱 Integer.valueOf(42)
int primitive = boxed; // 拆箱 boxed.intValue()⚠️ 自动装箱有成本:每次装箱可能创建新对象;循环中大量装箱会造成 GC 压力,性能敏感代码应优先用基本类型。
Integer 默认缓存 -128 到 127 的实例(Integer.valueOf 命中缓存直接返回):
java
Integer x = 100;
Integer y = 100;
System.out.println(x == y); // true,命中缓存
Integer m = 200;
Integer n = 200;
System.out.println(m == n); // false,超出缓存范围,各自 new缓存上限可通过 -XX:AutoBoxCacheMax 调整,但默认行为是 -128..127。
工程建议:包装类型之间比较一律用 equals(),不要依赖缓存行为判断 ==;Long、Short、Byte 也有同样的 -128..127 缓存。
33.8 BigDecimal 深入
第 5 章提到金额用 BigDecimal,这里补「为什么」和「怎么用」。
double 用二进制浮点近似表示十进制小数,0.1 无法精确存储:
java
System.out.println(0.1 + 0.2); // 0.30000000000000004BigDecimal 用「任意精度十进制」表示,适合金额等需要精确十进制的场景。
关键 API:
java
BigDecimal price = new BigDecimal("19.99"); // 用字符串构造,避免 double 传入
BigDecimal qty = new BigDecimal("3");
BigDecimal total = price.multiply(qty); // 加减乘除都返回新对象
BigDecimal rounded = total.setScale(2, RoundingMode.HALF_UP);要点:
- 用字符串构造:
new BigDecimal("0.1")精确;new BigDecimal(0.1)会把 double 的近似值带进来。 BigDecimal不可变,运算返回新对象,要接收返回值。- 比较用
compareTo()而不是equals():equals会比较 scale(小数位),new BigDecimal("1.0")和new BigDecimal("1.00")equals为 false 但compareTo为 0。 - 除法必须指定精度或舍入模式,否则遇到除不尽会抛
ArithmeticException。
33.9 Optional 深入
第 18 章已讲基础,这里补常用陷阱与技巧。
不要用 Optional 作为方法参数或字段(Java 官方不建议),它主要用于「方法的返回值可能为空」:
java
Optional<User> findUser(Long id);链式转换:map 处理有值的情况,flatMap 处理返回 Optional 的情况:
java
String city = userRepository.findById(id)
.map(User::address)
.map(Address::city)
.orElse("未知城市");orElse 与 orElseGet 的区别:orElse 的参数无论是否有值都会被求值,orElseGet 是惰性的:
java
// 即使 user 存在,buildDefaultUser() 也会执行(浪费)
User u1 = optional.orElse(buildDefaultUser());
// 只有 user 不存在时才执行
User u2 = optional.orElseGet(() -> buildDefaultUser());⚠️ get() 在没有值时会抛 NoSuchElementException,除非确定有值,否则用 orElse / orElseGet / orElseThrow。Optional 不是为了「少写一个 null 判断」,而是为了把「可能为空」显式化。
33.10 日期时间深入:Instant、Duration、Period 与格式化
第 23 章已介绍 java.time 基础,这里补「时间点 / 时间量 / 格式化」三个常用维度。
时间点 Instant 表示 UTC 时间轴上的瞬间,适合存储和传输:
java
Instant now = Instant.now();
long epochSecond = now.getEpochSecond();
Instant parsed = Instant.parse("2026-09-01T00:00:00Z");时间量:Duration 表示「秒/纳秒」的时间量,Period 表示「年/月/日」:
java
Duration d = Duration.between(start, end); // 例如 PT1H30M
Period p = Period.between(localDate1, localDate2); // 例如 P1Y2M3D
Duration timeout = Duration.ofSeconds(30);
boolean expired = elapsed.compareTo(timeout) > 0;格式化用 DateTimeFormatter(线程安全,可复用):
java
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String text = LocalDateTime.now().format(fmt);
LocalDateTime dt = LocalDateTime.parse("2026-09-01 10:00:00", fmt);要点:
- 时间戳、跨时区事件用
Instant+ UTC 存储,展示时再转本地时区。 Duration/Period不可变,运算返回新对象。DateTimeFormatter可以定义为常量复用,避免每次调用ofPattern的开销。SimpleDateFormat线程不安全,已是被淘汰的思路,新代码统一用DateTimeFormatter。
快速记忆清单
- Java 代码编译成字节码,在 JVM 上运行。
- JDK 是开发工具包,JVM 是运行字节码的虚拟机。
- Java 是强类型语言,适合大型工程。
- 面向对象核心是封装、继承、多态,但实际项目中优先接口和组合。
- 集合、泛型、异常、Stream 是日常开发高频能力。
- Maven/Gradle 类似 Java 生态的依赖管理和构建系统。
- Spring 项目理解 Java 类、接口、注解、泛型和异常处理非常重要。
- 进阶后要理解线程池、JVM 内存和性能排查。