Skip to content

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 基础 ​

  1. Java 是什么
  2. JDK、JRE、JVM
  3. 安装与第一个程序
  4. 基本语法
  5. 数据类型和变量
  6. 运算符和流程控制

第二篇:面向对象 ​

  1. 类和对象
  2. 封装、继承、多态
  3. 接口和抽象类
  4. static、final、访问修饰符
  5. record、enum、sealed class

第三篇:常用语言能力 ​

  1. String 和常用类
  2. 异常处理
  3. 泛型
  4. 集合框架
  5. Lambda 和函数式接口
  6. Stream API
  7. Optional

第四篇:工程与后端基础 ​

  1. 包、模块和项目结构
  2. Maven 和 Gradle
  3. 单元测试
  4. I/O 和 NIO
  5. 日期时间 API
  6. JSON 处理

第五篇:并发、JVM 与进阶 ​

  1. 线程和线程池
  2. synchronized、Lock、volatile
  3. CompletableFuture
  4. JVM 内存模型
  5. 垃圾回收
  6. 性能排查
  7. 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. 数据类型和变量 ​

基本类型:

类型含义
byte8 位整数
short16 位整数
int32 位整数
long64 位整数
float32 位浮点
double64 位浮点
char字符
booleantrue/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");

常用类:

  • BigDecimal
  • LocalDateTime
  • Optional
  • UUID
  • Objects
  • Collections
  • List
  • Map

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:队列。

常用实现:

  • ArrayList
  • LinkedList
  • HashSet
  • TreeSet
  • HashMap
  • ConcurrentHashMap

示例:

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();

常用操作:

  • filter
  • map
  • flatMap
  • sorted
  • distinct
  • limit
  • collect
  • groupingBy

分组:

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.demo

Spring 项目常见结构:

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 package

Gradle 使用 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. 性能排查 ​

常见工具:

  • jps
  • jstack
  • jmap
  • jcmd
  • Java Flight Recorder
  • VisualVM
  • Arthas

排查方向:

  • CPU 高:看线程栈。
  • 内存高:看堆 dump。
  • 接口慢:看数据库、外部调用、锁和线程池。
  • GC 频繁:看对象分配和内存泄漏。

31. Java 与 Spring 学习路线 ​

Java 学习优先级:

  1. 基础语法和面向对象。
  2. 集合、泛型、异常。
  3. Lambda、Stream、Optional。
  4. Maven/Gradle 和测试。
  5. I/O、JSON、日期时间。
  6. 并发基础。
  7. JVM 基础。
  8. Spring Boot。
  9. Spring MVC、Spring Data JPA、Spring Security。
  10. 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
并发读写 MapConcurrentHashMap

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。

简化过程:

  1. 调用 key 的 hashCode() 得到 hash。
  2. 根据数组长度计算桶下标。
  3. 如果桶为空,直接放入。
  4. 如果桶不为空,使用 equals() 判断是否同一个 key。
  5. 如果 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 正确时是 1

32.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
ErrorJVM 或系统级严重问题,一般不捕获

业务项目中常见分层:

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 关注三件事:

  1. 哪些对象还活着。
  2. 如何回收不活跃对象。
  3. 如何减少停顿和吞吐损失。

排查思路:

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

intern() 可以把字符串放入常量池并返回池中引用:

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.30000000000000004

BigDecimal 用「任意精度十进制」表示,适合金额等需要精确十进制的场景。

关键 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 内存和性能排查。

基于 VitePress 构建的个人学习文档知识库