Java 作为一门历史悠久且持续演进的编程语言,自其诞生以来,便不断通过新版本的发布引入众多创新特性,以适应现代软件开发的需求。本文将详尽地剖析 Java 8 至今(直至 Java 21 作为当前主流 LTS 版本)各重要版本所带来的核心新特性,旨在帮助开发者理解这些特性如何提升开发效率、代码质量及程序性能。

核心思想:理解 Java 各版本的新特性,能够使开发者编写出更现代、更简洁、更高性能的代码,并有效利用 JVM 的最新优化。


一、Java 8 (LTS - 发布于 2014 年)

Java 8 是 Java 发展史上的一个里程碑版本,引入了大量旨在提升生产力的新特性,尤其是在函数式编程和并发领域。

1.1 Lambda 表达式

定义:Lambda 表达式提供了一种简洁的方式来表示可传递的匿名函数。它使得函数可以作为方法参数,并且使代码更加简洁、可读性更强。这实质上是支持了函数式编程范式。

语法(parameters) -> expression(parameters) -> { statements; }

示例 (Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 旧版本匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello from a thread (old way).");
}
}).start();

// Java 8 Lambda 表达式
new Thread(() -> System.out.println("Hello from a thread (Lambda).")).start();

// 集合迭代
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));

1.2 Stream API

定义:Stream API 提供了一种处理集合(以及其他数据源)的声明式、函数式的方式。它允许对集合进行一系列管道化的操作(如过滤、映射、排序、收集),而无需显式管理迭代器的状态。Stream 本身不存储数据,它只是数据源的视图。

示例 (Java)

1
2
3
4
5
6
7
8
9
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 筛选偶数,乘以2,然后收集到一个新的列表中
List<Integer> evenDoubled = numbers.stream()
.filter(n -> n % 2 == 0) // 中间操作:过滤
.map(n -> n * 2) // 中间操作:映射
.collect(Collectors.toList()); // 终端操作:收集

System.out.println(evenDoubled); // 输出: [4, 8, 12, 16, 20]

1.3 接口的默认方法 (Default Methods)

定义:允许在接口中定义带有实现体的方法。这使得在不破坏现有实现类的情况下,可以向接口添加新方法,从而实现接口的“向后兼容”扩展。默认方法使用 default 关键字修饰。

示例 (Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface MyInterface {
void abstractMethod();

default void defaultMethod() {
System.out.println("这是接口的默认方法实现。");
}
}

class MyClass implements MyInterface {
@Override
public void abstractMethod() {
System.out.println("这是实现类的抽象方法实现。");
}
}

// 使用
MyInterface obj = new MyClass();
obj.abstractMethod(); // 输出: 这是实现类的抽象方法实现。
obj.defaultMethod(); // 输出: 这是接口的默认方法实现。

1.4 Optional 类

定义java.util.Optional 是一个容器对象,可能包含也可能不包含非 null 值。它旨在解决 NullPointerException (NPE) 问题,鼓励开发者显式地处理可能缺失的值,从而提高代码的健壮性和可读性。

示例 (Java)

1
2
3
4
5
6
String name = getOptionalName().orElse("DefaultName"); // 如果 Optional 为空,则使用"DefaultName"
System.out.println(name);

// 避免 NPE
Optional<String> maybeName = Optional.ofNullable(someMethodReturningNullableString());
maybeName.ifPresent(n -> System.out.println("Name is: " + n)); // 只有当值存在时才执行

1.5 新的日期和时间 API (java.time)

定义:Java 8 引入了一套全新的、现代化、线程安全的日期和时间 API (JSR 310),替代了老旧且设计不佳的 java.util.Datejava.util.Calendar。它位于 java.time 包中,提供了 LocalDate (日期), LocalTime (时间), LocalDateTime (日期时间), ZonedDateTime (带时区), Instant (时间戳), Duration (持续时间) 和 Period (时间段) 等类。

示例 (Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LocalDate today = LocalDate.now();
System.out.println("今天的日期: " + today);

LocalTime currentTime = LocalTime.now();
System.out.println("当前时间: " + currentTime);

LocalDateTime now = LocalDateTime.now();
System.out.println("当前日期时间: " + now);

Instant timestamp = Instant.now();
System.out.println("当前时间戳: " + timestamp);

// 计算两天后的日期
LocalDate twoDaysLater = today.plusDays(2);
System.out.println("两天后: " + twoDaysLater);

二、Java 9 (发布于 2017 年)

Java 9 的最大亮点是模块化系统,旨在解决大型应用中的依赖管理问题,并提升 JVM 性能。

2.1 Java 平台模块系统 (JPMS - Project Jigsaw)

定义:JPMS 旨在通过模块化地组织 JDK 和应用程序,提高 Java 应用的可伸缩性、安全性和性能。它引入了 module 关键字,允许开发者定义模块及其依赖关系。

主要目标

  • 可靠的配置:显式声明模块间的依赖关系,防止运行时出现类路径问题。
  • 强封装:模块可以明确指定哪些包是公共的,哪些是私有的。
  • 可伸缩性:允许 JVM 在运行时只加载应用程序真正需要的模块。

示例 (Java - module-info.java)

1
2
3
4
5
// module-info.java
module com.example.app {
requires com.example.service; // 声明对 com.example.service 模块的依赖
exports com.example.app.main; // 导出包,允许其他模块访问
}

2.2 JShell (交互式 Java Shell)

定义:JShell 是一个交互式的命令工具,允许开发者在不创建完整 Java 文件和编译的情况下,直接输入 Java 代码片段并立即执行,类似于 Python 或 JavaScript 的解释器。这对于学习 Java 语法、测试 API 或快速原型开发非常有用。

示例 (JShell 命令)

1
2
3
4
5
6
7
8
9
10
$ jshell
| Welcome to JShell -- Version 9
| For an introduction type /help intro

jshell> int x = 10;
x ==> 10
jshell> int y = x * 2;
y ==> 20
jshell> System.out.println("Result: " + y);
Result: 20

2.3 接口私有方法 (Private Interface Methods)

定义:Java 9 允许在接口中定义私有方法。这些私有方法可以被接口中的默认方法或静态方法调用,主要用于分解和复用复杂默认方法内部的公共代码逻辑,而无需将这些实现细节暴露给实现类。

示例 (Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface MyService {
default void doSomething() {
commonLogic(); // 调用私有方法
System.out.println("Doing something specific.");
}

static void staticHelper() {
staticCommonLogic(); // 调用私有静态方法
System.out.println("Doing static helper task.");
}

private void commonLogic() { // 私有实例方法
System.out.println("Common logic for default methods.");
}

private static void staticCommonLogic() { // 私有静态方法
System.out.println("Common static logic for static methods.");
}
}

三、Java 10 (发布于 2018 年)

Java 10 引入了局部变量类型推断,显著简化了代码。

3.1 局部变量类型推断 (Local-Variable Type Inference - var)

定义:通过 var 关键字,在声明局部变量时,编译器可以根据初始化表达式自动推断变量的类型。这使得代码更加简洁,减少了冗余的类型声明。

限制var 只能用于局部变量、for 循环中的变量和 try-with-resources 语句中。不能用于字段、方法参数、方法返回类型或 Lambda 表达式参数。

示例 (Java)

1
2
3
4
5
6
7
8
9
// 旧版本
String message = "Hello, Java!";
List<String> names = new ArrayList<>();
for (int i = 0; i < 10; i++) { /* ... */ }

// Java 10 var
var message = "Hello, Java!"; // 编译器推断为 String
var names = new ArrayList<String>(); // 编译器推断为 ArrayList<String>
for (var i = 0; i < 10; i++) { /* ... */ } // 编译器推断为 int

四、Java 11 (LTS - 发布于 2018 年)

Java 11 是一个重要的 LTS 版本,带来了新的 HTTP 客户端、字符串方法和 Lambda 表达式参数的 var 关键字支持。

4.1 全新的 HTTP 客户端 (Standard)

定义java.net.http 包中提供了一套现代化的 HTTP 客户端 API,支持 HTTP/1.1 和 HTTP/2,并且可以同步或异步发送请求。它提供了更友好的 API 和更好的性能,逐渐取代 HttpURLConnection

示例 (Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class HttpClientExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/get"))
.GET()
.build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

System.out.println("Status code: " + response.statusCode());
System.out.println("Body: " + response.body());
}
}```

### 4.2 Lambda 表达式参数的 `var` 关键字

**定义**:Java 11 扩展了 `var` 关键字的使用范围,允许在 Lambda 表达式的参数中使用 `var` 来推断类型。这在需要对 Lambda 参数进行注解时非常有用。

**示例 (Java)**:
```java
import java.util.function.Function;

public class VarInLambdaExample {
public static void main(String[] args) {
// 在 Lambda 参数上使用 var,常用于添加注解
Function<String, String> processor = (@Deprecated var s) -> s.toUpperCase();
System.out.println(processor.apply("hello")); // 输出: HELLO
}
}

4.3 字符串新方法

定义:在 String 类中新增了一系列处理空白字符和重复字符串的实用方法。

  • isBlank(): 判断字符串是否为空或者只包含空白字符。
  • strip(): 移除字符串前导和尾随的空白字符(Unicode 空白字符)。
  • stripLeading(): 移除字符串前导的空白字符。
  • stripTrailing(): 移除字符串尾随的空白字符。
  • repeat(int count): 将字符串重复 count 次。

示例 (Java)

1
2
3
4
String text = "  Hello World  \t ";
System.out.println("isBlank: " + text.isBlank()); // false
System.out.println("strip: '" + text.strip() + "'"); // 'Hello World'
System.out.println("repeat: " + "abc".repeat(3)); // abcabcabc

五、Java 14 (发布于 2020 年)

Java 14 开始引入了一些影响深远的预览特性,其中 Records 和 Pattern Matching for instanceof 最受关注。

5.1 Records (预览)

定义:Records 是一种新的 Java 类型声明,用于创建不可变的数据类。它显著简化了数据类的编写,自动生成了构造函数、equals()hashCode()toString() 等方法,避免了大量样板代码。它本质上是聚合了数据的有限状态。

目标:减少编写数据载体类(data carrier class)的冗余代码。

示例 (Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 旧版本需要大量样板代码的 User 类
/*
public class User {
private final String name;
private final int age;

public User(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() { return name; }
public int getAge() { return age; }

@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
@Override
public String toString() { ... }
}
*/

// Java 14 Records
public record User(String name, int age) {}

// 使用
public class RecordExample {
public static void main(String[] args) {
User user1 = new User("Alice", 30);
User user2 = new User("Alice", 30);

System.out.println(user1); // User[name=Alice, age=30]
System.out.println(user1.name()); // Alice (注意是 name() 而不是 getName())
System.out.println(user1.equals(user2)); // true
}
}

5.2 instanceof 的模式匹配 (Pattern Matching for instanceof - 预览)

定义:该特性简化了 instanceof 操作后的类型转换。在 instanceof 表达式中,如果测试成功,可以直接声明一个绑定变量,而无需进行额外的强制类型转换。

示例 (Java)

1
2
3
4
5
6
7
8
9
10
11
12
Object obj = "Hello Java";

// 旧版本
if (obj instanceof String) {
String s = (String) obj; // 需要强制类型转换
System.out.println(s.length());
}

// Java 14 模式匹配 for instanceof
if (obj instanceof String s) { // 自动绑定变量 s,并且类型为 String
System.out.println(s.length()); // 可以直接使用 s
}

六、Java 17 (LTS - 发布于 2021 年)

Java 17 是继 Java 11 之后的又一个长期支持版本,它将一些预览特性进行标准化,并巩固了 JVM 的核心能力。

6.1 Records (标准化)

定义:Records 在 Java 17 中被正式标准化。其特性与预览版本相同,用于创建不可变的数据类。

6.2 instanceof 的模式匹配 (Pattern Matching for instanceof - 标准化)

定义:该特性在 Java 17 中被正式标准化,提供更简洁的类型检查和转换语法。

6.3 密封类 (Sealed Classes - 标准化)

定义:密封类 (sealed classes) 是一种限制类或接口层次结构的新机制。它允许一个类或接口明确指定哪些类或接口可以扩展或实现它。这使得编译器可以在编译时检查类型层次结构是否完整,从而提供更强的约束和代码安全性。

语法:使用 sealed 关键字修饰类,并使用 permits 关键字列出允许继承/实现该密封类的子类。子类必须使用 finalsealednon-sealed 关键字进行修饰。

示例 (Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 定义一个密封接口 Shape
public sealed interface Shape permits Circle, Square, Triangle {
double area();
}

// Circle 是一个终态子类
public final class Circle implements Shape {
private final double radius;
public Circle(double radius) { this.radius = radius; }
@Override public double area() { return Math.PI * radius * radius; }
}

// Square 是一个密封子类,它还可以有自己的子类
public sealed class Square implements Shape permits ColoredSquare {
private final double side;
public Square(double side) { this.side = side; }
@Override public double area() { return side * side; }
}

// ColoredSquare 是 Square 的终态子类
public final class ColoredSquare extends Square {
private final String color;
public ColoredSquare(double side, String color) { super(side); this.color = color; }
// ...
}

// Triangle 是一个非密封子类,它允许任何类继承它
public non-sealed class Triangle implements Shape {
private final double base;
private final double height;
public Triangle(double base, double height) { this.base = base; this.height = height; }
@Override public double area() { return 0.5 * base * height; }
}

好处:这在处理有穷联合类型(algebraic data types)时非常有用,例如配合 switch 表达式可以实现穷举检查。

七、Java 21 (LTS - 发布于 2023 年)

Java 21 是最新的 LTS 版本,它带来了虚拟线程、序列化集合等重磅特性,进一步提升了并发编程效率和语言表达力。

7.1 虚拟线程 (Virtual Threads - 标准化)

定义:虚拟线程 (Project Loom / JEP 444) 旨在通过提供轻量级线程来大幅简化并发编程和提高应用程序的可伸缩性。与传统的平台线程(由操作系统管理)不同,虚拟线程由 JVM 而非操作系统调度,创建成本极低,可以创建数百万个,极大地避免了线程池的复杂性。

核心优势

  • 高吞吐量:显著提升需要管理大量并发连接的服务器应用程序的吞吐量。
  • 简化编程:开发者可以像使用传统线程一样编写阻塞代码,而无需引入复杂的异步编程模型(如 CompletableFuture、响应式编程)。
  • 栈空间优化:虚拟线程的栈占用远小于传统线程。

示例 (Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.time.Duration;
import java.util.concurrent.Executors;

public class VirtualThreadExample {
public static void main(String[] args) throws InterruptedException {
// 使用 Executors.newVirtualThreadPerTaskExecutor() 创建虚拟线程池
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Executing task " + taskId + " on virtual thread: " + Thread.currentThread());
try {
Thread.sleep(Duration.ofSeconds(1)); // 模拟阻塞 IO 操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
} // executor.close() 会等待所有虚拟线程完成

System.out.println("All virtual threads submitted. Waiting for completion...");
Thread.sleep(Duration.ofSeconds(12)); // 主线程等待一段时间,观察输出
System.out.println("Main thread exiting.");
}
}

在 JEP 444 中提到, 通过 Thread.ofVirtual().start(runnable) 也可以直接创建和启动虚拟线程。

7.2 序列化集合 (Sequenced Collections - 标准化)

定义:Java 21 引入了 SequencedCollectionSequencedSetSequencedMap 三个新接口,它们定义了访问集合第一个和最后一个元素、以及反转集合顺序的方法。这些接口为所有具有明确元素顺序的集合(如 ListDequeLinkedHashSetLinkedHashMap 等)提供了统一且明确的 API,解决了这些操作在不同实现中 API 不一致的问题。

新的接口

  • SequencedCollection<E>: 提供了 getFirst(), getLast(), addFirst(), addLast(), removeFirst(), removeLast(), reversed() 方法。
  • SequencedSet<E>: 继承自 SequencedCollectionSet,增加了 reversed()
  • SequencedMap<K, V>: 提供了 firstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry(), reversed() 方法。

示例 (Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.ArrayList;
import java.util.SequencedCollection;
import java.util.List;
import java.util.LinkedHashMap;
import java.util.SequencedMap;

public class SequencedCollectionsExample {
public static void main(String[] args) {
// List 实现了 SequencedCollection
SequencedCollection<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
System.out.println("Original: " + names); // Original: [Alice, Bob, Charlie]

names.addFirst("David");
names.addLast("Eve");
System.out.println("After add: " + names); // After add: [David, Alice, Bob, Charlie, Eve]

System.out.println("First: " + names.getFirst()); // First: David
System.out.println("Last: " + names.getLast()); // Last: Eve

SequencedCollection<String> reversedNames = names.reversed();
System.out.println("Reversed: " + reversedNames); // Reversed: [Eve, Charlie, Bob, Alice, David]

// LinkedHashMap 实现了 SequencedMap
SequencedMap<String, Integer> scores = new LinkedHashMap<>();
scores.put("Alice", 90);
scores.put("Bob", 85);
scores.put("Charlie", 95);

System.out.println("First entry: " + scores.firstEntry()); // First entry: Alice=90
System.out.println("Last entry: " + scores.lastEntry()); // Last entry: Charlie=95

SequencedMap<String, Integer> reversedScores = scores.reversed();
System.out.println("Reversed map: " + reversedScores); // Reversed map: {Charlie=95, Bob=85, Alice=90}
}
}

7.3 记录模式 (Record Patterns - 标准化)

定义:记录模式允许开发者在 instanceof 表达式和 switch 语句中对 Record 类型进行解构。这使得直接从 Record 对象中提取其组件变得更加简洁和直观。

示例 (Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public record Point(int x, int y) {}
public record Rectangle(Point topLeft, Point bottomRight) {}

public class RecordPatternsExample {
public static void main(String[] args) {
Object obj = new Point(10, 20);

// 使用记录模式进行 instanceof 检查和解构
if (obj instanceof Point(int x, int y)) { // 直接解构 Point 对象的 x 和 y 组件
System.out.println("Point coordinates: (" + x + ", " + y + ")");
}

Object shape = new Rectangle(new Point(0, 0), new Point(100, 50));

// 在 switch 语句中使用记录模式
String description = switch (shape) {
case Point(int x, int y) -> "这是一个点,坐标为 (" + x + ", " + y + ")";
case Rectangle(Point topLeft, Point bottomRight) ->
"这是一个矩形,左上角为 (" + topLeft.x() + ", " + topLeft.y() +
"),右下角为 (" + bottomRight.x() + ", " + bottomRight.y() + ")";
default -> "未知图形";
};
System.out.println(description);
}
}

7.4 字符串模板 (String Templates - 预览)

定义:字符串模板 (JEP 430) 旨在简化字符串的拼接和格式化,通过模板化文本嵌入表达式,增强可读性和安全性。这类似于其他语言(如 JavaScript 的模板字面量、Python 的 F-string)。

语法STR."...",其中 STR 是模板处理器,点后是双引号包围的模板文本,模板文本中可以通过 \{expression} 嵌入表达式。

示例 (Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 这是预览特性,需要特定编译和运行参数,或者在 IDE 中开启预览功能
// javac --enable-preview -source 21 StringTemplateExample.java
// java --enable-preview StringTemplateExample

import static java.lang.StringTemplate.STR; // 静态导入 STR 模板处理器

public class StringTemplateExample {
public static void main(String[] args) {
String name = "Alice";
int age = 30;
double price = 19.99;

// 拼接字符串和变量
String greeting = STR."Hello, my name is \{name} and I am \{age} years old.";
System.out.println(greeting); // Hello, my name is Alice and I am 30 years old.

// 支持任意表达式
String productInfo = STR."The total price for 2 items is \{2 * price}.";
System.out.println(productInfo); // The total price for 2 items is 39.98.
}
}

7.5 未命名类和实例 Main 方法 (Unnamed Classes and Instance Main Methods - 预览)

定义:(JEP 445) 旨在简化 Java 程序的编写,特别是对于初学者和小型程序。它允许类声明可以没有显式名称(未命名类),并且可以直接在类中定义一个非静态的 main 方法作为程序的入口点,从而减少了样板代码。

示例 (Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 这是预览特性,需要特定编译和运行参数,或者在 IDE 中开启预览功能
// 编译: javac --enable-preview --release 21 MyUnnamedClass.java
// 运行: java --enable-preview MyUnnamedClass

// 传统的 Java 程序入口
/*
public class MyProgram {
public static void main(String[] args) {
System.out.println("Hello, Java!");
}
}
*/

// Java 21 中的未命名类和实例 main 方法
// 无需显式声明公共类或静态 main 方法
void main() {
System.out.println("Hello, World from an unnamed class!");
// 可以访问类的实例字段和方法
int value = someMethod();
System.out.println("Value: " + value);
}

int someMethod() {
return 123;
}

这对于快速编写脚本、练习代码或编写一些工具类非常方便。

八、总结

Java 从 Java 8 的函数式编程和 Stream API,到 Java 9 的模块化,再到 Java 10 及之后版本对 var 关键字、Records、密封类、虚拟线程、序列化集合和模式匹配等特性的持续投入,展现了其作为一门现代语言不断演进的生命力。这些新特性极大地提升了 Java 的表达力、开发效率和运行性能:

  • 函数式编程:Lambda 和 Stream API 彻底改变了集合处理和并发编程的范式。
  • 代码简洁性var 关键字、Records、模式匹配和字符串模板减少了大量的样板代码。
  • 模块化与安全性:JPMS 和 Sealed Classes 提升了大型应用的结构化和安全性。
  • 并发性能:虚拟线程是 Java 在高并发场景下的一个革命性突破。
  • API 现代化:新的日期时间 API、HTTP 客户端和序列化集合提供了更健壮、更一致的库功能。

作为 Java 开发者,持续学习和掌握这些新特性至关重要,它们不仅能够帮助编写出更加优雅、高效的代码,也能更好地应对未来软件开发中的挑战。