从 JDK 1.7 到 25:十三年 Java 新特性实用指南(附代码 + Spring Boot 兼容)

3 阅读10分钟

一句话省流
JDK 8 的 Lambda/Stream 让你写出更简洁的代码;JDK 17 的 Record 消灭 DTO 的 getter/setter;JDK 21 的虚拟线程让高并发 I/O 性能飙升;JDK 25 的紧凑对象头能节省 30% 内存。


📌 快速查阅(点击跳转)


📌 JDK 版本怎么选?一张表搞定

生产环境原则:只用 LTS 版本(8、11、17、21、25)。非 LTS 版本寿命仅 6 个月,不适合线上。

版本发布年是否 LTS定位生产建议
72011语法糖补全已淘汰
82014函数式革命存量项目主流,新项目不推荐
112018小特性 LTS过渡版本
172021x现代 Java 基石当前新建项目首选
212023高并发大杀器强烈推荐 I/O 密集型
252025性能 + AI 增强面向未来,适合云原生

🚀 JDK 7 (2011):那些你可能不知道的语法糖

JDK 7 虽然常被忽略,但它引入的几个特性每天都在被无数 Java 开发者使用。

1️⃣ try-with-resources —— 告别 finally 里关流

痛点:以前操作文件/数据库连接,必须在 finally 里手动 close(),代码冗长还容易漏掉。

// ❌ 旧写法(JDK 6 及以前)
BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("data.txt"));
    System.out.println(br.readLine());
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (br != null) {
        try { br.close(); } catch (IOException e) { e.printStackTrace(); }
    }
}
// ✅ JDK 7+:自动关闭,清爽到哭
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
    System.out.println(br.readLine());
} catch (IOException e) {
    e.printStackTrace();
}
// 多个资源用分号隔开,关闭顺序与声明顺序相反

实战注意:只有实现了 AutoCloseableCloseable 的对象才能这样用。Java 7 之后,所有 I/O 流、JDBC 的 ConnectionStatementResultSet 都支持。
一个隐藏优化:如果 try 块和 close() 都抛出异常,close() 的异常会被抑制,可通过 Throwable.getSuppressed() 获取。

2️⃣ 菱形操作符 <> —— 少写一半泛型代码

// ❌ 以前
Map<String, List<Integer>> map = new HashMap<String, List<Integer>>();

// ✅ 现在
Map<String, List<Integer>> map = new HashMap<>();

注意:JDK 7 只支持在声明时用 <>,JDK 9 之后才允许匿名内部类中使用。

3️⃣ 数字字面量下划线 —— 一眼看出是多少钱

int balance = 10_000_000;          // 一千万
long cardNo = 1234_5678_9012_3456L;
double pi = 3.1415_9265;

约定:通常每三位一组(金额)、每四位一组(卡号)、每两位一组(字节)。

🔥 JDK 8 (2014):改变 Java 命运的一次升级

JDK 8 是 Java 历史上最重要的版本,没有之一。下面几个特性彻底改变了我们的编码方式。

1️⃣ Lambda 表达式 —— 让代码成为数据

背景:以前传递一段行为必须用匿名内部类,啰嗦得让人不想写。

// ❌ 匿名内部类
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
}).start();

// ✅ Lambda(一行搞定)
new Thread(() -> System.out.println("Hello")).start();

语法速记

  • 无参数:() -> 表达式
  • 单参数(可省略类型):x -> x*2
  • 多参数:(a,b) -> a + b
  • 多行语句必须用 {} 并写 return

闭包陷阱:Lambda 引用的局部变量必须是 final 或 effectively final(即赋值后不再改变)。不要试图在 Lambda 内部修改外部循环变量。

2️⃣ Stream API —— 集合操作的“流水线”

Stream 让你用声明式的方式处理集合,代码可读性暴增。

// 需求:获取年龄>18的用户,按年龄倒序,取前3个,只留姓名
List<String> result = users.stream()
    .filter(u -> u.getAge() > 18)
    .sorted(Comparator.comparingInt(User::getAge).reversed())
    .limit(3)
    .map(User::getName)
    .collect(Collectors.toList());

效率提示

  • stream() 是串行,parallelStream() 是并行(大数据集才有收益)。
  • 尽量避免在 filter 之后立即 sorted,那样会破坏并行性。
  • 终止操作(collectforEachreduce)才会真正执行,中间操作是惰性的。

3️⃣ Optional —— 拯救 null 指针

痛点NullPointerException 是 Java 的头号克星。

// ❌ 链式调用遇到 null 就爆炸
String city = user.getAddress().getCity();

// ✅ 优雅处理
String city = Optional.ofNullable(user)
    .map(User::getAddress)
    .map(Address::getCity)
    .orElse("未知城市");

最佳实践

  • 只作为返回值,不要用作方法参数或字段。
  • ✅ 用 orElseGet(Supplier) 代替 orElse 来延迟执行(避免不必要的开销)。
  • ❌ 别调用 Optional.get() 而不检查 isPresent()

4️⃣ 新的日期时间 API —— 别再忍受 Date

java.util.Date 的设计可谓反人类:月份从 0 开始、线程不安全、时区混乱。

// ❌ 旧方式
Calendar cal = Calendar.getInstance();
cal.set(2024, 0, 15);  // 1月要写0,醉了
Date date = cal.getTime();

// ✅ 新方式
LocalDate date = LocalDate.of(2024, Month.JANUARY, 15);
LocalDateTime now = LocalDateTime.now();
LocalDate nextWeek = LocalDate.now().plusWeeks(1);
Period age = Period.between(birthday, LocalDate.now());

注意LocalDateTime 不带时区,如果需要带时区用 ZonedDateTime;数据库交互建议用 Instant

⚡ JDK 11 (2018):首个“小而美”的 LTS

JDK 11 重在稳定和增强,没有大刀阔斧的语法改动,但有几个生产力特性值得记住。

1️⃣ 直接运行 .java 文件

java Hello.java   # 无需 javac 编译,适合写脚本或快速测试

适用场景:写一次性工具、教学演示、快速验证算法。不会产生 .class 文件。

2️⃣ HTTP Client —— 内置的现代化客户端

以前我们要用 Apache HttpClient 或 OkHttp,现在 JDK 自带。

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.github.com/users/octocat"))
    .timeout(Duration.ofSeconds(10))
    .header("Accept", "application/json")
    .GET()
    .build();

// 同步请求
HttpResponse<String> resp = client.send(request, HttpResponse.BodyHandlers.ofString());

// 异步请求(返回 CompletableFuture)
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
      .thenAccept(resp -> System.out.println(resp.body()));

优势:原生支持 HTTP/2、WebSocket、异步非阻塞。

3️⃣ var —— 局部变量类型推断(实际 JDK 10 引入,11 稳定)

var map = new HashMap<String, List<Integer>>();   // 推断为 HashMap<...>
var list = List.of(1, 2, 3);

⚠️ 三个不能用的地方

  1. 方法参数和返回值
  2. 成员变量
  3. 赋值为 null(无法推断)

建议:只在类型名明显冗余时使用(如 new HashMap<>()),不要滥用导致可读性下降。

4️⃣ 集合不可变工厂方法

List<String> list = List.of("A", "B", "C");   // 不可变
list.add("D");  // 抛出 UnsupportedOperationException

适用于:常量集合、配置项。

🛡️ JDK 17 (2021):现代 Java 开发的标准答案

JDK 17 汇集了 JDK 9~16 中预览多年的特性,正式成为企业级开发的“新基线”。

1️⃣ Record —— 杀死 POJO 样板代码

一个 DTO 需要构造器、getter、equals、hashCode、toString 几十行代码?Record 一行搞定。

public record User(Long id, String name, Integer age) {}

// 使用
User user = new User(1L, "张三", 25);
System.out.println(user.name());  // 注意:不是 getName()
System.out.println(user);         // User[id=1, name=张三, age=25]

高级用法:添加验证逻辑。

public record Product(String name, BigDecimal price) {
    public Product {  // 紧凑构造器
        if (price.compareTo(BigDecimal.ZERO) < 0)
            throw new IllegalArgumentException("价格不能为负");
    }
}

Record vs Lombok:Record 无外部依赖、不可变,适合作为数据传输对象(DTO)、API 返回对象;Lombok 更灵活但需要插件。

2️⃣ Switch 表达式 —— 终于不用写 break 了

// ✅ 箭头 + 表达式返回
String type = switch (day) {
    case 1, 2, 3, 4, 5 -> "工作日";
    case 6, 7 -> "周末";
    default -> throw new IllegalArgumentException();
};

// 需要多行语句时用 yield
String result = switch (value) {
    case 1 -> "一";
    default -> {
        System.out.println("其他");
        yield "未知";
    }
};

模式匹配(JDK 21 正式):

Object obj = "Hello";
String desc = switch (obj) {
    case Integer i -> "整数 " + i;
    case String s && s.length() > 5 -> "长字符串";
    case String s -> "短字符串";
    case null -> "null值";
    default -> "未知";
};

3️⃣ 文本块 —— 告别转义地狱

写 JSON、SQL、HTML 时,无需再加一堆 \n 和引号转义。

String json = """
    {
      "name": "Java 17",
      "features": ["文本块", "Record"]
    }
    """;

注意:文本块的开头的 """ 后面不能直接跟非空白字符,否则会编译错误。

🧵 JDK 21 (2023):并发编程的革命

JDK 21 最大的亮点是虚拟线程,它让 Java 在处理高并发 I/O 时的性能直追 Go 和 Erlang。

1️⃣ 虚拟线程 —— 百万并发不是梦

传统线程的痛:每个 Java 线程对应一个 OS 线程,占用约 1MB 栈内存,创建销毁成本高。一旦并发数上万,系统就不堪重负。

虚拟线程:由 JVM 管理的轻量线程,一个 OS 线程可以挂载成千上万个虚拟线程,阻塞时自动让出载体线程,切换开销极低。

// 创建虚拟线程(JDK 21)
Thread vThread = Thread.ofVirtual().start(() -> {
    System.out.println("我是虚拟线程");
});

// 生产级用法:虚拟线程池
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 100_000; i++) {
        executor.submit(() -> handleRequest());
    }
} // 自动等待所有任务完成

何时使用

  • I/O 密集型:数据库查询、远程调用、文件读写 → 效果爆炸
  • CPU 密集型:密集计算任务 → 仍用传统线程池

避坑:不要在虚拟线程中使用 synchronized 块,它会固定载体线程,导致性能下降。建议用 ReentrantLock 替代。

2️⃣ 结构化并发 —— 让并发代码像同步一样明确

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Subtask<String> user = scope.fork(() -> fetchUser());
    Subtask<Integer> order = scope.fork(() -> fetchOrders());
    scope.join();               // 等待所有子任务
    scope.throwIfFailed();      // 任一失败则抛出异常
    return new Result(user.get(), order.get());
} // 退出作用域时,未完成的任务自动取消

好处:线程生命周期与代码块绑定,防止线程泄漏,错误处理自然。

🌟 JDK 25 (2025):性能与 AI 时代的 LTS

JDK 25 重点在性能优化和现代化密码学支持。

1️⃣ 紧凑对象头 —— 减少 30% 内存占用

java -XX:+UseCompactObjectHeaders -jar app.jar

原理:传统 JVM 对象头占 12~16 字节(压缩指针下),JDK 25 将其压缩到 8 字节。对于缓存海量对象的系统(比如 Session、商品 SKU),内存占用显著下降,GC 次数减少。

2️⃣ 灵活构造器体 —— 终于可以在 super() 前写验证了

public class PositiveNumber extends Number {
    PositiveNumber(int value) {
        if (value <= 0) {   // JDK 25 允许在 super 前执行验证
            throw new IllegalArgumentException("必须为正数");
        }
        super(value);
    }
}

之前只能写在 super() 后面,那时对象已经部分构造,不太优雅。

3️⃣ 基本类型模式匹配(预览)

Object obj = 42L;
switch (obj) {
    case int x -> System.out.println("int: " + x);
    case long l -> System.out.println("long: " + l);
    default -> System.out.println("其他");
}

不再需要手动拆箱,switch 直接识别原始类型。

📌 Spring Boot 各版本该配哪个 JDK?

Spring Boot 版本最低 JDK兼容 JDK关键说明
2.x (2.4~2.7)88~172023 年底已停止维护,新项目慎用
3.0~3.41717~23强制 JDK 17+,改用 Jakarta EE
3.4+1717~23支持虚拟线程 (Tomcat 开启 spring.threads.virtual.enabled=true)
4.0 (2025.11)1717~25原生镜像优先,Jakarta EE 11

升级决策

  • 存量 JDK 8 + Boot 2.x → 一步到位升级到 JDK 21 + Boot 3.4,同时获得虚拟线程 + Record + 文本块。
  • 新建项目 → JDK 21 + Spring Boot 3.4(除非公司强制要求 17)。
  • 追求极致启动速度和内存 → JDK 25 + Spring Boot 4.0 + GraalVM 原生镜像。

💡 升级路线图 + 最值得学的 5 个特性

升级难度评估

当前版本推荐目标难度主要工作量
JDK 7JDK 17⭐⭐⭐⭐⭐语法变更 + 第三方库兼容
JDK 8JDK 21⭐⭐⭐⭐模块化可能引入的问题,但大部分代码兼容
JDK 11JDK 21⭐⭐顺滑升级,注意 var 和新的 GC 参数
JDK 17JDK 25开启实验性参数即可享受到对象头压缩

新特性影响力 TOP 5(实用角度)

排名特性版本一句话价值
🥇Lambda + Stream8让你的集合操作代码减少 70%
🥈虚拟线程21高并发 Web 服务吞吐量提升 10 倍
🥉Record17消灭 DTO 所有模板代码
4Switch 表达式 + 模式匹配17/21写出更安全的业务分支
5新日期时间 API8告别 Date 和 Calendar 的坑