Java中各个LTS版本新特性梳理

一、Java8新特性梳理

1. Lambda 表达式

Lambda 表达式是 Java 8 最引人注目的特性,它允许你将功能作为方法参数,或者将代码本身当作数据。它提供了一种清晰且简洁的方式来表示一个方法接口(通常是函数式接口)的实例。

语法:
(parameters) -> expression

(parameters) -> { statements; }

示例:

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
// 1. 无参数,无返回值
Runnable r1 = () -> System.out.println("Hello World!");

// 2. 一个参数,无返回值 (参数可省略括号)
Consumer<String> consumer = (msg) -> System.out.println(msg);
// 等价于
Consumer<String> consumerSimplified = msg -> System.out.println(msg);

// 3. 多个参数,有返回值
Comparator<Integer> comparator = (a, b) -> {
System.out.println("Comparing " + a + " and " + b);
return a.compareTo(b);
};
// 等价于 (单表达式可省略 return 和花括号)
Comparator<Integer> comparatorSimplified = (a, b) -> a.compareTo(b);

// 4. 使用示例:替代匿名内部类
// Java 7 方式
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Old way");
}
}).start();

// Java 8 Lambda 方式
new Thread(() -> System.out.println("New way")).start();

2. 函数式接口

函数式接口是只有一个抽象方法的接口。它可以用作 Lambda 表达式的类型。Java 8 提供了 @FunctionalInterface 注解来显式标识一个接口是函数式接口。

常见的函数式接口:

  • Runnable -> run()
  • Callable<V> -> call()
  • Comparator<T> -> compare(T o1, T o2)
  • **Consumer<T>**: 消费一个参数,无返回值 (T t) -> void
  • **Supplier<T>**: 不取参数,返回一个值 () -> T
  • **Function<T, R>**: 接受一个参数,返回一个结果 (T t) -> R
  • **Predicate<T>**: 接受一个参数,返回布尔值 (T t) -> boolean

示例:

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
// 自定义函数式接口
@FunctionalInterface
interface MyFunctionalInterface {
void doSomething(String s);
}

public class Main {
public static void main(String[] args) {
// 使用自定义函数式接口
MyFunctionalInterface myFunc = s -> System.out.println(s.toUpperCase());
myFunc.doSomething("hello"); // 输出: HELLO

// 使用内置 Consumer
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Hello Consumer");

// 使用内置 Predicate
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(4)); // 输出: true
System.out.println(isEven.test(5)); // 输出: false

// 使用内置 Function
Function<String, Integer> stringToLength = str -> str.length();
System.out.println(stringToLength.apply("Java")); // 输出: 4

// 使用内置 Supplier
Supplier<Double> randomSupplier = () -> Math.random();
System.out.println(randomSupplier.get());
}
}

3. 方法引用与构造器引用

方法引用提供了一种更简洁的 Lambda 表达式,用于直接指向已有方法。它们是 Lambda 的语法糖。

语法:

  • object::instanceMethod -> System.out::println
  • Class::staticMethod -> Math::max
  • Class::instanceMethod -> String::length (第一个参数成为方法的目标)
  • Class::new (构造器引用) -> ArrayList::new

示例:

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
import java.util.Arrays;
import java.util.List;
import java.util.function.*;

public class MethodReferenceDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 1. 对象::实例方法
// Lambda: (s) -> System.out.println(s)
names.forEach(s -> System.out.println(s));
// 方法引用:
names.forEach(System.out::println);

// 2. 类::静态方法
// Lambda: (a, b) -> Math.max(a, b)
BiFunction<Integer, Integer, Integer> maxFunc = Math::max;
System.out.println(maxFunc.apply(10, 20));

// 3. 类::实例方法
// Lambda: (s1, s2) -> s1.compareTo(s2)
Comparator<String> comparator = String::compareTo;
System.out.println(comparator.compare("A", "B")); // -1

// Lambda: (str) -> str.length()
Function<String, Integer> lengthFunc = String::length;
System.out.println(lengthFunc.apply("Java")); // 4

// 4. 构造器引用
// Lambda: () -> new ArrayList<>()
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> newList = listSupplier.get();
}
}

4. Stream API

java.util.stream.Stream 是 Java 8 处理集合(Collection)的关键抽象概念。它可以对集合进行非常复杂的查找、过滤、映射数据等操作。Stream 操作分为中间操作(返回 Stream)和终端操作(返回具体结果或副作用)。

特点:

  • 不是数据结构:它不存储数据,而是通过管道从源(如集合、数组)获取数据。
  • 函数式编程:对流的操作会产生结果,但不会修改源数据。
  • 惰性执行:中间操作总是惰性的,终端操作是触发实际计算的触发器。
  • 可消费性:Stream 只能被消费一次。

操作类型:

  • 中间操作filter, map, flatMap, distinct, sorted, peek, limit, skip
  • 终端操作forEach, collect, count, anyMatch, allMatch, noneMatch, findFirst, findAny, reduce, min, max

示例:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import java.util.*;
import java.util.stream.*;

public class StreamDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Michael", "Dean", "James", "Chris", "Michael");

// 1. 过滤、去重、遍历
names.stream()
.filter(name -> name.startsWith("J")) // 过滤出以 J 开头的
.distinct() // 去重
.forEach(System.out::println); // 输出: James

// 2. 映射 (map - 一对一, flatMap - 一对多并展开)
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase) // 将每个元素映射为其大写形式
.collect(Collectors.toList()); // 收集为 List
System.out.println(upperCaseNames); // [MICHAEL, DEAN, JAMES, CHRIS, MICHAEL]

List<List<String>> listOfLists = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d")
);
List<String> flatList = listOfLists.stream()
.flatMap(Collection::stream) // 将多个流扁平化为一个流
.collect(Collectors.toList());
System.out.println(flatList); // [a, b, c, d]

// 3. 排序和限制
names.stream()
.sorted() // 自然排序
.limit(3) // 取前 3 个
.forEach(System.out::println); // Chris, Dean, James

// 4. 匹配与查找
boolean anyStartsWithC = names.stream().anyMatch(name -> name.startsWith("C"));
System.out.println("Any name starts with 'C': " + anyStartsWithC); // true

Optional<String> first = names.stream().findFirst();
first.ifPresent(name -> System.out.println("First element: " + name)); // Michael

// 5. 归约 (Reduce) - 将流元素组合起来
Optional<String> concatenated = names.stream().reduce((s1, s2) -> s1 + "#" + s2);
concatenated.ifPresent(System.out::println); // Michael#Dean#James#Chris#Michael

// 6. 收集 (Collect) - 强大的终端操作,常用 Collectors 工具类
Map<Integer, List<String>> groupedByLength = names.stream()
.collect(Collectors.groupingBy(String::length)); // 按字符串长度分组
System.out.println(groupedByLength); // {4=[Dean], 5=[James, Chris], 7=[Michael, Michael]}

String joinedString = names.stream()
.collect(Collectors.joining(", ", "[", "]")); // 连接字符串
System.out.println(joinedString); // [Michael, Dean, James, Chris, Michael]
}
}

5. Optional 类

java.util.Optional<T> 是一个容器对象,可能包含也可能不包含非 null 值。它旨在避免显式的 null 检查,从而减少 NullPointerException

示例:

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
38
39
40
41
42
43
44
import java.util.Optional;

public class OptionalDemo {
public static void main(String[] args) {
// 创建 Optional 对象
Optional<String> emptyOpt = Optional.empty(); // 创建一个空 Optional
Optional<String> ofOpt = Optional.of("Hello"); // 值不能为 null,否则立即抛出 NPE
Optional<String> ofNullableOpt = Optional.ofNullable(nullableMethod()); // 值可以为 null

// 常用方法
String possibleNullValue = nullableMethod();
// 传统方式 (容易产生 NPE)
// if (possibleNullValue != null) {
// System.out.println(possibleNullValue.length());
// }

// Optional 方式
Optional.ofNullable(possibleNullValue)
.ifPresent(value -> System.out.println(value.length())); // 值存在才执行操作

String result = Optional.ofNullable(possibleNullValue)
.orElse("Default Value"); // 如果为空,返回默认值
System.out.println(result);

String result2 = Optional.ofNullable(possibleNullValue)
.orElseGet(() -> someExpensiveOperation()); // 惰性提供默认值
System.out.println(result2);

// 链式调用和异常抛出
String upperCased = Optional.ofNullable(possibleNullValue)
.map(String::toUpperCase) // 如果值不为空,就应用函数
.orElseThrow(() -> new IllegalArgumentException("Value cannot be null")); // 为空则抛出异常
System.out.println(upperCased);
}

private static String nullableMethod() {
// 可能返回 null 的方法
return Math.random() > 0.5 ? "Some Data" : null;
}

private static String someExpensiveOperation() {
return "Expensively Generated Default";
}
}

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

Java 8 引入了全新的日期时间 API (java.time 包) 来修正旧 DateCalendar 类的诸多问题。它是基于 Joda-Time 库设计的,并且是不可变的和线程安全的。

核心类:

  • LocalDate: 只包含日期,例如 2023-10-27
  • LocalTime: 只包含时间,例如 15:30:00
  • LocalDateTime: 包含日期和时间
  • ZonedDateTime: 包含时区的日期时间
  • Instant: 时间戳(Unix 时间)
  • Duration: 时间段,基于时间(秒,纳秒)
  • Period: 时间段,基于日期(年,月,日)
  • DateTimeFormatter: 格式化和解析日期时间

示例:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

public class DateTimeDemo {
public static void main(String[] args) {
// 1. 获取当前日期/时间
LocalDate today = LocalDate.now();
LocalTime now = LocalTime.now();
LocalDateTime currentDateTime = LocalDateTime.now();
System.out.println("Today: " + today);
System.out.println("Now: " + now);
System.out.println("Current DateTime: " + currentDateTime);

// 2. 创建特定日期/时间
LocalDate specificDate = LocalDate.of(2020, Month.JANUARY, 1);
LocalTime specificTime = LocalTime.of(12, 0, 0);
LocalDateTime specificDateTime = LocalDateTime.of(2020, 1, 1, 12, 0);
System.out.println("Specific Date: " + specificDate);
System.out.println("Specific Time: " + specificTime);

// 3. 操作日期时间 (不可变,每次操作产生新对象)
LocalDate tomorrow = today.plusDays(1);
LocalDate nextWeek = today.plusWeeks(1);
LocalDate previousMonthSameDay = today.minus(1, ChronoUnit.MONTHS);
System.out.println("Tomorrow: " + tomorrow);

// 4. 比较日期时间
boolean isAfter = tomorrow.isAfter(today);
System.out.println("Is tomorrow after today? " + isAfter); // true

// 5. 时区时间
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println("Time in New York: " + zonedDateTime);

// 6. 时间戳 (Instant)
Instant instant = Instant.now();
System.out.println("Current Timestamp: " + instant);

// 7. 时间段
Duration duration = Duration.between(specificTime, now);
System.out.println("Duration between then and now: " + duration.toHours() + " hours");

Period period = Period.between(specificDate, today);
System.out.println("Period: " + period.getYears() + " years, " + period.getMonths() + " months");

// 8. 格式化与解析
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = currentDateTime.format(formatter);
System.out.println("Formatted: " + formattedDateTime);

LocalDateTime parsedDateTime = LocalDateTime.parse("2023-10-27 14:30:00", formatter);
System.out.println("Parsed: " + parsedDateTime);
}
}

7. 接口的默认方法和静态方法

Java 8 允许在接口中添加默认方法(使用 default 关键字)和静态方法

  • 默认方法: 允许向现有接口添加新功能,同时确保与为这些接口的旧版本编写的代码的二进制兼容性。它提供了默认实现。
  • 静态方法: 允许在接口中定义静态工具方法,可以通过接口名直接调用。

示例:

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
public interface Vehicle {
// 传统的抽象方法
void start();

// 默认方法 - 提供默认实现
default void honk() {
System.out.println("Vehicle is honking!");
}

// 静态方法 - 工具方法
static int getHorsepower(int rpm, int torque) {
return (rpm * torque) / 5252;
}
}

public class Car implements Vehicle {
@Override
public void start() {
System.out.println("Car is starting...");
}
// 可以选择重写 honk(),也可以不重写而使用默认实现
}

public class Demo {
public static void main(String[] args) {
Car car = new Car();
car.start(); // 实现抽象方法
car.honk(); // 调用默认方法

// 调用接口静态方法
int hp = Vehicle.getHorsepower(6000, 300);
System.out.println("Horsepower: " + hp);
}
}

8. Nashorn JavaScript 引擎

Java 8 引入了新的 JavaScript 引擎 Nashorn,取代了旧的 Rhino 引擎。它提供了更好的性能,并允许在 JVM 上直接运行 JavaScript 代码。

示例 (在 Java 代码中执行 JS):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import javax.script.*;

public class NashornDemo {
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");

// 执行 JavaScript 代码
engine.eval("print('Hello from JavaScript!');");

// 执行 JS 函数并从 Java 调用
engine.eval("function greet(name) { return 'Hello, ' + name; }");
Invocable invocable = (Invocable) engine;
Object result = invocable.invokeFunction("greet", "Java Developer");
System.out.println(result); // 输出: Hello, Java Developer
}
}

注意:从 Java 11 开始,Nashorn 已被标记为废弃,并在后续版本中移除。


总结表格

特性 描述 主要优点
Lambda 表达式 简洁的匿名函数表示法 代码更简洁,便于函数式编程
函数式接口 只有一个抽象方法的接口 Lambda 表达式的类型基础
方法引用 更简洁地指向已有方法 进一步简化 Lambda 表达式
Stream API 对数据源进行函数式操作 强大的集合处理能力,并行支持
Optional 容器类,可能包含也可能不包含值 避免 NullPointerException
新的日期时间 API java.time 包下的新类 清晰、不可变、线程安全
接口默认/静态方法 接口中可以包含实现的方法 增强接口能力,保持向后兼容
Nashorn JavaScript 新的 JS 引擎 更好的 JS 执行性能 (已在 JDK 11+ 移除)

二、Java11新特性梳理

Java 11 作为继 Java 8 之后的第二个长期支持(LTS)版本,于2018年9月发布。它带来了不少便利的特性、性能提升和改进。我来为你梳理一下 Java 11 的主要新特性,并提供相应的代码示例。

🧪 1. 局部变量类型推断增强(Lambda 参数支持 var

Java 10 引入了局部变量类型推断 (var),Java 11 将其扩展到了 Lambda 表达式的参数中。这使得你可以在 Lambda 参数中使用 var 声明,并且可以附加注解(例如 @Nullable)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.List;
import java.util.Arrays;

public class VarInLambda {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 在 Lambda 表达式中使用 var 声明参数(可以添加注解)
names.forEach((@NotNull var name) -> System.out.println(name));

// 多个参数时,所有参数都必须使用 var,不能混合使用
// BiConsumer<String, String> example = (var s1, var s2) -> System.out.println(s1 + s2);
}
}

说明

  • 并非为了省略类型(Lambda 中类型本身可省略),而是为了能够在参数上添加注解(如 @Nullable, @Nonnull)来提供更强的类型检查约束。
  • 使用 var 时,所有参数都必须使用 var,不能部分使用 var 部分显式声明类型。

🔤 2. 字符串增强方法

Java 11 为 String 类新增了几个非常实用的方法,用于简化常见的字符串操作。

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
public class StringMethods {
public static void main(String[] args) {
String str = " \tHello Java 11\n ";

// isBlank(): 检查字符串是否为空白(空或仅包含空白字符,支持 Unicode)
System.out.println(str.isBlank()); // false (因为中间有内容)
System.out.println(" ".isBlank()); // true

// strip(): 去除字符串首尾的空白字符(比 trim() 更强大,支持 Unicode)
String stripped = str.strip();
System.out.println("'" + stripped + "'"); // 'Hello Java 11'

// stripLeading(): 去除首部空白
// stripTrailing(): 去除尾部空白

// lines(): 返回一个由行分割的 Stream<String>
String multiLine = "Line 1\nLine 2\r\nLine 3";
multiLine.lines().forEach(System.out::println);
// 输出:
// Line 1
// Line 2
// Line 3

// repeat(int count): 将字符串重复 count 次
String java = "Java ";
System.out.println(java.repeat(3)); // "Java Java Java "
}
}

🌐 3. 标准 HTTP 客户端 API

Java 11 将 Java 9 中引入并在 Java 10 中更新的孵化器 HTTP 客户端 API (jdk.incubator.http) 进行了标准化,并将其纳入 java.net.http 包。这个新的客户端支持 HTTP/1.1 和 HTTP/2,以及 WebSocket,提供了同步异步两种调用方式。

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
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;

public class HttpClientExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // 使用 HTTP/2
.connectTimeout(Duration.ofSeconds(10))
.build();

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/get"))
.GET()
.header("User-Agent", "Java11-HttpClient")
.build();

// 1. 同步发送请求
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("同步响应状态码: " + response.statusCode());
System.out.println("同步响应体: " + response.body());

// 2. 异步发送请求
CompletableFuture<HttpResponse<String>> futureResponse =
client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
futureResponse.thenApply(HttpResponse::body)
.thenAccept(body -> System.out.println("异步响应体: " + body))
.join(); // 等待异步操作完成(仅用于示例,实际应用中可能不需要)
}
}

📁 4. 单文件源代码程序直接运行

Java 11 允许你直接运行单个 .java 文件,而无需先使用 javac 显式编译。这对于快速测试小脚本或教学示例非常方便。

HelloWorld.java:

1
2
3
4
5
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello,直接运行单文件源码!");
}
}

在命令行中直接运行:

1
java HelloWorld.java

输出:

1
Hello,直接运行单文件源码!

说明

  • Java 虚拟机会在内存中编译并执行该源文件。
  • 这个功能不适用于有多个类定义或依赖复杂的大型项目。

🗑️ 5. Epsilon:低开销垃圾收集器 (“No-Op”)

Java 11 引入了一个实验性的无操作 (No-Op) 垃圾收集器 —— Epsilon GC。它只负责内存分配,不进行任何垃圾回收。当堆内存耗尽时,JVM 会关闭。

应用场景

  • 性能测试:用于对比其他 GC 的开销,排除 GC 活动对性能测试结果的干扰。
  • 超短生命周期任务:任务运行时间极短,在堆内存耗尽前就会结束,无需垃圾回收。
  • VM 接口测试极端延迟优化(由开发者完全控制内存生命周期)。

使用方式(需添加 JVM 参数):

1
java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xmx1g MyApplication

✈️ 6. Flight Recorder 集成

Java Flight Recorder (JFR) 之前是 Oracle JDK 中的商业功能,在 Java 11 中集成到了 OpenJDK,并且可以免费使用。JFR 是一个高性能的性能分析和诊断工具,可用于收集 JVM 和应用程序运行时的详细数据,而对性能的影响很小。

启动 JFR 录制(可以在应用启动时或运行时通过 jcmd 触发):

1
java -XX:StartFlightRecording=duration=60s,filename=myrecording.jfr,settings=profile MyApplication

说明

  • 录制的数据文件 (myrecording.jfr) 可以使用 JDK Mission Control (JMC) 工具进行分析,可视化地查看 CPU 使用、内存分配、锁竞争、I/O 等信息。

🔒 7. 嵌套基于访问控制(Nest-Based Access Control)

这是一个 JVM 级别的改进,旨在简化嵌套类(如内部类)之间对私有成员的访问,并减少编译器生成的合成桥接方法(synthetic bridge methods)。

示例
在 Java 11 之前,下面的代码可能会由编译器生成一些额外的“桥接”方法来让 Inner 访问 Outer 的私有字段。Java 11 引入了 nests 的概念,使这种访问更加直接自然。

1
2
3
4
5
6
7
8
9
public class Outer {
private int outerValue = 10;

class Inner {
int accessOuter() {
return outerValue; // Java 11 中,内部类访问外部类的私有成员更加高效
}
}
}

说明

  • 这主要是一个底层改进,通常不需要修改你的代码,但可以使生成的字节码更简洁,并可能带来微小的性能提升。

🔐 8. TLS 1.3 支持

Java 11 支持 TLS 1.3 协议,这是一个更安全、性能也更优的新版本传输层安全协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import javax.net.ssl.SSLContext;
import java.security.NoSuchAlgorithmException;

public class TLS13Example {
public static void main(String[] args) throws NoSuchAlgorithmException {
// 获取默认的 SSLContext(通常会使用平台默认的协议,Java 11+ 默认可能包含 TLSv1.3)
SSLContext defaultContext = SSLContext.getDefault();
System.out.println("默认协议: " + defaultContext.getProtocol());

// 明确指定使用 TLSv1.3
SSLContext tls13Context = SSLContext.getInstance("TLSv1.3");
tls13Context.init(null, null, null);
System.out.println("明确指定 TLSv1.3: " + tls13Context.getProtocol());
}
}

TLS 1.3 的优点

  • 更强的安全性:移除了一些不安全的加密算法,简化了握手过程以减少攻击面。
  • 更快的连接速度:握手过程所需的往返次数 (Round-Trip Time, RTT) 减少,连接建立更快。

📊 9. 集合 API 增强

Java 11 为集合接口(List, Set, Map)添加了一些便利的工厂方法,用于创建不可变的集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.List;
import java.util.Set;
import java.util.Map;

public class CollectionFactories {
public static void main(String[] args) {
// 创建不可变列表
List<String> immutableList = List.of("Java", "Python", "C++");
// immutableList.add("Go"); // 尝试修改会抛出 UnsupportedOperationException

// 创建不可变集合
Set<Integer> immutableSet = Set.of(1, 2, 3, 4, 5);

// 创建不可变映射 (最多10个键值对)
Map<String, Integer> immutableMap = Map.of("One", 1, "Two", 2, "Three", 3);

// 创建更多键值对的映射,可以使用 Map.ofEntries
// Map<String, Integer> largerMap = Map.ofEntries(
// entry("A", 1),
// entry("B", 2),
// // ... 更多条目
// );
}
}

说明

  • 这些方法 (List.of, Set.of, Map.of) 创建的是不可变集合,任何尝试修改的操作都会导致 UnsupportedOperationException
  • 它们为快速创建小型集合提供了便利,替代了传统的 Arrays.asList 或手动构建的方式。

📝 10. 其他值得注意的变更

  • 移除和废弃的模块
    • 移除了 Java EE (如 javax.xml.ws) 和 CORBA 模块。如果你需要这些功能,需要手动添加相关的依赖(例如 JAX-WS RI)。
    • 废弃了 Nashorn JavaScript 引擎 (考虑迁移到 GraalVM 的 JavaScript 实现)。
    • 移除了 JavaFX,它已成为一个独立的项目。
  • Unicode 10 支持:更新支持 Unicode 10.0 标准,新增了字符和符号。
  • Files 类新增读写字符串方法:方便地进行小文本文件的读写。
    1
    2
    3
    4
    5
    import java.nio.file.*;

    Path path = Files.writeString(Path.of("test.txt"), "Hello Java 11!");
    String content = Files.readString(path);
    System.out.println(content); // Hello Java 11!

💎 总结与建议

Java 11 作为长期支持版本,不仅提供了语言特性和 API 的增强(如 HTTP 客户端、字符串方法、局部变量推断增强),还在性能(如 ZGC、JFR)、安全性(TLS 1.3)和开发体验(单文件运行)方面带来了重要改进。

下面的表格帮你快速回顾 Java 11 的主要新特性:

特性类别 核心特性 说明与收益
语言语法 Lambda 参数使用 var 支持在 Lambda 参数上添加注解
新增字符串方法 (isBlank, lines, repeat, strip) 简化字符串操作,支持 Unicode 空白字符
API 增强 标准 HTTP 客户端 支持 HTTP/2, 同步/异步调用,取代 HttpURLConnection
集合工厂方法 (List.of, Set.of, Map.of) 快速创建不可变集合
工具与JVM 单文件源码直接运行 无需 javac 编译,直接 java HelloWorld.java
Epsilon 垃圾收集器 实验性 No-Op GC,用于性能测试等特殊场景
Flight Recorder (JFR) 集成 高性能性能监控工具,集成到 OpenJDK
嵌套基于访问控制 JVM 底层优化,简化嵌套类私有访问
安全性与其他 TLS 1.3 支持 更安全、更快的安全协议
移除 Java EE & CORBA 模块 JDK 更轻量,需手动添加依赖

三、Java17新特性梳理

🧬 1. 密封类 (Sealed Classes)

密封类(接口)允许你明确规定哪些其他类或接口可以扩展或实现它。这提供了对继承层次结构的更强控制,有助于建模固定的领域,并增强代码的安全性。

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
38
39
40
41
42
43
44
// 定义一个密封类 Shape,只允许 Circle 和 Rectangle 继承
public sealed class Shape permits Circle, Rectangle {
public abstract double area();
}

// final 类 Circle,是 Shape 的子类
public final class Circle extends Shape {
private final double radius;

public Circle(double radius) {
this.radius = radius;
}

@Override
public double area() {
return Math.PI * radius * radius;
}
}

// non-sealed 类 Rectangle,是 Shape 的子类,允许被其他类继承
public non-sealed class Rectangle extends Shape {
private final double width;
private final double height;

public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}

@Override
public double area() {
return width * height;
}
}

// 由于 Rectangle 是 non-sealed,所以可以继承它
public class Square extends Rectangle {
public Square(double side) {
super(side, side);
}
}

// 尝试继承一个未在 permits 子句中声明的类会导致编译错误
// public class Triangle extends Shape { } // 编译错误

说明

  • 使用 sealed 关键字修饰类或接口。
  • 使用 permits 关键字指定允许继承的子类。
  • 子类必须是 final, sealednon-sealed 之一。
  • 密封类通常与模式匹配(如 instanceofswitch)结合使用,编译器可以检查是否覆盖了所有允许的子类。

🔍 2. 模式匹配 (Pattern Matching)

模式匹配简化了对象类型检查和转换的代码编写,使代码更简洁、安全。

instanceof 模式匹配

在 Java 17 中,instanceof 可以直接将匹配的对象转换为目标类型并赋值给一个变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class PatternMatchingExample {
public static void main(String[] args) {
Object obj = "Hello, Java 17!";

// 旧写法:需要显式转换
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str.toUpperCase());
}

// Java 17 新写法:直接在 instanceof 中声明变量
if (obj instanceof String str) { // 如果 obj 是 String 类型,则直接赋值给 str
System.out.println(str.toUpperCase());
} else {
System.out.println("Not a string");
}
}
}

Switch 表达式模式匹配(预览功能)

虽然在 Java 17 中 Switch 表达式模式匹配仍为预览功能,但值得了解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SwitchPatternMatching {
public static void main(String[] args) {
Object obj = 42L;

// 传统的 switch 语句
String message;
switch (obj) {
case Integer i -> message = "Integer: " + i;
case Long l -> message = "Long: " + l;
case String s -> message = "String: " + s;
default -> message = "Unknown type";
}
System.out.println(message);

// 使用 switch 表达式 (Java 14 引入) 结合模式匹配 (预览)
// String result = switch (obj) {
// case Integer i -> "Integer: " + i;
// case Long l -> "Long: " + l;
// case String s -> "String: " + s;
// default -> "Unknown type";
// };
// System.out.println(result);
}
}

注意:要使用预览功能,编译和运行时需添加 --enable-preview 参数。

📝 3. 文本块 (Text Blocks)

文本块简化了多行字符串(如 JSON、HTML、SQL)的编写,无需大量的转义和连接操作。

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
public class TextBlocksExample {
public static void main(String[] args) {
// 旧写法:使用转义和连接符
String oldJson = "{\n" +
" \"name\": \"John\",\n" +
" \"age\": 30\n" +
"}";

// Java 17 文本块:使用三重引号
String newJson = """
{
"name": "John",
"age": 30
}
""";
System.out.println(newJson);

// 文本块也支持格式化方法
String name = "Alice";
int age = 25;
String formattedJson = """
{
"name": "%s",
"age": %d
}
""".formatted(name, age);
System.out.println(formattedJson);
}
}

说明

  • 文本块以 """ 开始,以 """ 结束。
  • 编译器会自动处理缩进和换行。
  • 可以使用 formatted 方法进行格式化,类似于 String.format()

🧱 4. 不可变集合工厂方法

Java 17 提供了更简便的方法来创建不可变的集合(List, Set, Map)。

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
import java.util.List;
import java.util.Set;
import java.util.Map;

public class ImmutableCollections {
public static void main(String[] args) {
// 创建不可变 List
List<String> fruitList = List.of("Apple", "Banana", "Orange");
// fruitList.add("Grape"); // 尝试修改会抛出 UnsupportedOperationException

// 创建不可变 Set
Set<Integer> numberSet = Set.of(1, 2, 3, 4, 5);
// numberSet.add(6); // 尝试修改会抛出 UnsupportedOperationException

// 创建不可变 Map (最多10个键值对)
Map<String, Integer> ageMap = Map.of("Alice", 25, "Bob", 30, "Charlie", 35);
// ageMap.put("David", 40); // 尝试修改会抛出 UnsupportedOperationException

// 创建更多键值对的 Map,可以使用 Map.ofEntries
// Map<String, Integer> largerMap = Map.ofEntries(
// Map.entry("Key1", 1),
// Map.entry("Key2", 2),
// Map.entry("Key3", 3)
// );

System.out.println(fruitList);
System.out.println(numberSet);
System.out.println(ageMap);
}
}

说明

  • 这些工厂方法创建的是不可修改的集合,任何修改操作都会导致 UnsupportedOperationException
  • 它们为创建小型集合提供了便利,并且通常比可变集合更节省内存。

🔐 5. 强封装 JDK 内部 API

Java 17 默认强封装了所有 JDK 的内部 API(如 sun.misc 包中的类)。这意味着除非显式打开包,否则无法通过反射等方式访问这些内部 API。这提高了安全性,鼓励开发者使用标准、公开的 API。

1
2
3
4
5
6
7
8
9
10
11
12
13
import sun.misc.Unsafe; // 在 Java 17 中,这会导致编译错误

public class InternalAPIAccess {
public static void main(String[] args) throws Exception {
// 尝试使用内部 API Unsafe
// Unsafe unsafe = Unsafe.getUnsafe(); // 编译错误

// 尝试通过反射访问内部 API
// Field field = Unsafe.class.getDeclaredField("theUnsafe");
// field.setAccessible(true);
// Unsafe unsafe = (Unsafe) field.get(null); // 在 Java 17 默认配置下运行时抛出异常
}
}

应对策略

  • 寻找并替换为公开的、标准的 API(例如,对于 Unsafe 的部分功能,可以考虑使用 VarHandle)。
  • 如果必须使用(例如某些第三方库暂时尚未更新),在启动 JVM 时可以使用 --add-opens 命令行参数来显式打开特定的模块和包,但这仅应作为临时手段

⚙️ 6. 垃圾收集器改进

Java 17 在垃圾收集器方面持续改进,特别是 ZGCShenandoah,它们的目标都是尽可能减少垃圾收集的停顿时间,适用于大内存和低延迟要求的应用场景。

  • ZGC (Z Garbage Collector): 一种可扩展的低延迟垃圾收集器。
  • Shenandoah: 一种低停顿时间的垃圾收集器。

使用方式(通过 JVM 启动参数启用):

1
2
3
4
5
# 启用 ZGC
java -XX:+UseZGC -Xmx2G MyApplication

# 启用 Shenandoah GC
java -XX:+UseShenandoahGC -Xmx2G MyApplication

说明

  • 这些 GC 适用于需要低延迟(停顿时间短)的应用,如游戏服务器、金融交易系统等。
  • 选择合适的 GC 需要根据应用的具体特点(内存分配模式、吞吐量要求等)进行测试和调优。

📦 7. jpackage 工具

jpackage 工具允许你将 Java 应用程序打包为特定于平台的本地格式,如 Windows 上的 .msi.exe,macOS 上的 .dmg.pkg,以及 Linux 上的 .deb.rpm。这极大地简化了 Java 应用的分发和安装。

基本用法
假设你有一个打包好的 JAR 应用 myapp.jar 和主类 com.example.Main

1
2
3
4
5
6
jpackage --name MyApplication \
--input input-lib/ \ # 包含所有依赖 JAR 的目录
--main-jar myapp.jar \
--main-class com.example.Main \
--type dmg \ # 打包类型 (dmg, pkg, exe, msi, deb, rpm)
--dest output/ # 输出目录

说明

  • jpackage 会创建一个包含** Java 运行时环境(JRE)** 的安装包,用户无需预先安装 Java。
  • 这使得 Java 应用的分发体验更接近原生应用。

💎 总结与建议

Java 17 作为长期支持版本,不仅提供了许多现代语言特性(如密封类、模式匹配),增强了代码的表达能力和安全性,还在性能(GC)、安全性(强封装内部 API)和分发体验(jpackage)方面带来了重要改进。

下面是一个表格,汇总了 Java 17 的主要新特性:

特性类别 核心特性 说明
语言语法 密封类 (Sealed Classes) 限制类的继承,提高代码安全性和可维护性
instanceof 模式匹配 (Pattern Matching for instanceof) 简化 instanceof 检查和类型转换
文本块 (Text Blocks) 简化多行字符串的编写,支持格式化
API 增强 不可变集合工厂方法 更方便地创建不可变集合 (List.of, Set.of, Map.of)
JVM 与性能 强封装 JDK 内部 API 默认禁止反射访问内部 API,提升安全性
垃圾收集器改进 (ZGC, Shenandoah) 降低垃圾收集暂停时间,提升吞吐量
工具与其他 jpackage 工具 将 Java 应用程序打包为本地安装包

升级建议

  • 对于新项目,强烈建议直接从 Java 17 开始,以利用其所有现代特性和长期支持。
  • 对于现有项目,升级到 Java 17 前需要:
    • 仔细测试,确保第三方库和框架与 Java 17 兼容。
    • 检查代码是否依赖了 JDK 内部 API,并进行重构。
    • 评估新特性(如密封类、模式匹配)是否能为现有代码带来好处。

四、Java21新特性梳理

Java 21 作为最新的长期支持(LTS)版本,带来了一系列令人兴奋的新特性,旨在提升开发效率、代码可读性、并发性能以及整体应用运行效率。下面我将为你梳理其主要新特性,并提供相应的示例代码。

下面表格汇总了 Java 21 的主要新特性:

特性类别 核心特性 简要说明
并发编程 虚拟线程 (Virtual Threads) - JEP 444 轻量级线程,由 JVM 管理,大幅提升高并发应用的性能和可扩展性
结构化并发 (Structured Concurrency) - JEP 453 简化并发编程,将多个子任务视为一个工作单元,提升可靠性和可观察性
语言语法与特性 记录模式 (Record Patterns) - JEP 440 在模式匹配中解构记录(Record)对象,简化数据访问
字符串模板 (String Templates) - 预览功能 JEP 430 简化字符串拼接,支持嵌入式表达式
未命名模式和变量 (Unnamed Patterns & Variables) 使用 _ 忽略不需要的模式或变量,减少冗余代码
API 增强 序列化集合 (Sequenced Collections) 为集合框架引入了新的接口和方法,以便更直观地访问首尾元素
JVM 与性能 分代式 ZGC (Generational ZGC) - JEP 439 ZGC 垃圾收集器引入分代机制,进一步提升垃圾回收效率,减少停顿时间
记录模式 (Record Patterns) - JEP 440 在模式匹配中解构记录(Record)对象,简化数据访问

接下来,我们详细看看这些新特性。

🧵 1. 虚拟线程 (Virtual Threads) - JEP 444

虚拟线程是 Java 21 中最具革命性的特性之一,旨在从根本上简化高并发应用的开发,并提升其可扩展性。它是一种由 JVM 管理的轻量级线程,创建和切换开销极低,因此可以轻松创建数百万个虚拟线程,而不会像传统平台线程那样耗尽资源。

传统线程模型的局限性
在 Java 21 之前,Java 使用的是平台线程(Platform Threads)模型,每个 Java 线程都直接映射到一个操作系统线程。这种模型下,线程的创建成本高、数量受限且上下文切换开销大,使得传统 Java 应用在高并发场景下容易遇到瓶颈。

虚拟线程的优势

  • 内存占用极低:每个虚拟线程初始仅需几百字节内存。
  • 创建数量几乎无限制:可轻松创建数百万个虚拟线程。
  • 由 JVM 调度:不绑定特定操作系统线程,切换开销极小。

创建虚拟线程的几种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1. 使用静态方法 Thread.startVirtualThread
Thread.startVirtualThread(() -> {
System.out.println("Hello from a virtual thread!");
});

// 2. 使用 Thread.ofVirtual()
Thread virtualThread = Thread.ofVirtual()
.name("my-virtual-thread")
.start(() -> {
System.out.println("Hello from a named virtual thread!");
});

// 3. 使用 Executors.newVirtualThreadPerTaskExecutor()
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " is running on a virtual thread.");
});
}
} // executor.close() 会等待所有提交的任务完成

性能对比(根据提供的测试数据):

指标 传统线程池 (200线程) 虚拟线程 (10000并发) 提升幅度
吞吐量 (requests/s) 3,200 12,800 300%
平均延迟 (ms) 62 15 75%降低
内存占用 (MB) 210 45 78%降低

最佳实践与注意事项

  • 适合I/O密集型任务:虚拟线程在等待I/O操作(如网络请求、数据库查询)时能够高效释放底层载体线程,非常适合处理大量并发I/O操作。
  • 计算密集型任务需谨慎:对于计算密集型任务(CPU-bound),虚拟线程的优势不明显,因为大量计算本身就会占用载体线程。
  • 避免同步阻塞操作:在虚拟线程内执行同步阻塞I/O操作(如传统的 java.net.Socket)会浪费其优势,应使用异步NIO API。
  • **谨慎使用 ThreadLocal**:由于虚拟线程数量可能极大,滥用 ThreadLocal 可能导致内存消耗增加。

🧩 2. 记录模式 (Record Patterns) - JEP 440

记录模式允许你在 instanceof 检查和 switch 表达式中直接解构(Destructure)记录(Record)对象,简洁地提取其组件,使代码更加清晰和安全。

示例:不使用记录模式 vs 使用记录模式

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
// 定义一个记录(Record)
record Point(int x, int y) {}
record Circle(Point center, int radius) {}

// 旧写法:需要显式调用访问器方法
Object obj = new Circle(new Point(10, 20), 30);
if (obj instanceof Circle c) {
Point center = c.center();
int x = center.x();
int y = center.y();
System.out.printf("Center: (%d, %d), Radius: %d%n", x, y, c.radius());
}

// Java 21 新写法:使用记录模式直接解构
if (obj instanceof Circle(Point(int x, int y), int radius)) {
// 变量 x, y, radius 已自动提取
System.out.printf("Center: (%d, %d), Radius: %d%n", x, y, radius);
}

// 在 switch 表达式中使用记录模式 (JEP 441)
String description = switch (obj) {
case Circle(Point(int x, int y), int r) ->
"Circle at (%d, %d) with radius %d".formatted(x, y, r);
case String s -> "It's a string: " + s;
default -> "Unknown object";
};
System.out.println(description);

📝 3. 字符串模板 (String Templates) - 预览功能 JEP 430

字符串模板是一种表达式插值特性,允许你将表达式直接嵌入字符串字面量中,使字符串拼接更加直观和安全(避免注入风险)。该特性在 Java 21 中为预览功能。

示例:传统拼接 vs 字符串模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String name = "Alice";
int age = 30;

// 旧写法:使用加号拼接或 String.format
String message1 = "Hello, " + name + ". You are " + age + " years old.";
String message2 = String.format("Hello, %s. You are %d years old.", name, age);

// Java 21 字符串模板(预览功能)
// 使用 STR 模板处理器
String message = STR."Hello, \{name}. You are \{age} years old.";
System.out.println(message); // 输出: Hello, Alice. You are 30 years old.

// 支持多行表达式和复杂表达式
int x = 10, y = 20;
String calculation = STR."\{x} + \{y} = \{x + y}";
System.out.println(calculation); // 输出: 10 + 20 = 30

🧭 4. 结构化并发 (Structured Concurrency) - JEP 453

结构化并发旨在简化并发编程,它将多个同时运行的任务(例如通过 fork)视为一个工作单元(通过 StructuredTaskScope)。这样,错误传播和取消变得更加可靠,任务的生命周期也更容易管理,从而避免了线程泄漏和取消延迟等问题。

核心思想

  • 任务层次结构:子任务的生命周期严格嵌套在父任务中。
  • 错误传播:任何子任务失败会取消所有同级任务并向父任务传播异常。
  • 资源管理:使用 try-with-resources 确保所有线程在作用域结束时完成。

示例:聚合多个微服务响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.concurrent.*;
import jdk.incubator.concurrent.StructuredTaskScope; // 注意:在Java 21中,JEP 453仍为预览API

Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 并发地 fork 子任务
Future<String> user = scope.fork(() -> findUser());
Future<Integer> order = scope.falk(() -> fetchOrder());

scope.join(); // 等待所有 fork 的任务完成(成功或失败)
scope.throwIfFailed(); // 如果任何子任务失败,则抛出 ExecutionException

// 此时所有任务都成功了,组合结果
return new Response(user.resultNow(), order.resultNow());
}
// 离开 try 块后,scope 会自动关闭,确保所有子任务都被取消(如果尚未完成)
}

在这个例子中,如果 findUserfetchOrder 中的任何一个失败,另一个任务会被自动取消。StructuredTaskScope 提供了两种策略:ShutdownOnFailure(任一失败则取消所有)和 ShutdownOnSuccess(任一成功则取消所有)。

🗑️ 5. 分代式 ZGC (Generational ZGC) - JEP 439

ZGC 是一款低延迟的垃圾收集器。Java 21 为其引入了分代机制(Generational ZGC),即区分年轻代(Young Generation)和老年代(Old Generation)。大部分对象都是“朝生夕死”的,分代后,ZGC 可以更频繁、更快速地收集年轻代,从而减少 Full GC 的次数,进一步降低垃圾收集的停顿时间,提升应用程序的吞吐量。

启用方式

1
2
3
4
5
# 启用分代式 ZGC (Java 21+)
java -XX:+UseZGC -XX:+ZGenerational ...

# 如果你想回退到非分代式 ZGC (在某些特殊情况下)
java -XX:+UseZGC -XX:-ZGenerational ...

🧰 6. 序列化集合 (Sequenced Collections)

Java 21 为集合框架引入了一组新的接口,用于表示具有明确遍历顺序的集合,并提供了更方便的方法来访问其第一个和最后一个元素

新增的核心接口

  • SequencedCollection<E>: 定义了具有序列的集合(如 LinkedList, LinkedHashSet)。
  • SequencedSet<E>: 不允许重复元素的 SequencedCollection(如 LinkedHashSet)。
  • SequencedMap<K,V>: 定义了具有序列的映射(如 LinkedHashMap)。

新方法示例

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
// SequencedCollection / SequencedSet
SequencedCollection<String> sequencedCollection = new LinkedHashSet<>();
sequencedCollection.add("Alice");
sequencedCollection.add("Bob");
sequencedCollection.add("Charlie");

// 获取第一个和最后一个元素
String first = sequencedCollection.getFirst(); // "Alice" (之前需要 .iterator().next())
String last = sequencedCollection.getLast(); // "Charlie" (之前需要遍历或其它操作)

// 在头部和尾部添加元素
sequencedCollection.addFirst("Zoe");
sequencedCollection.addLast("David");

// 移除并返回头部和尾部的元素
String removedFirst = sequencedCollection.removeFirst();
String removedLast = sequencedCollection.removeLast();

// SequencedMap
SequencedMap<String, Integer> sequencedMap = new LinkedHashMap<>();
sequencedMap.put("One", 1);
sequencedMap.put("Two", 2);
sequencedMap.put("Three", 3);

// 获取第一个和最后一个键值对
Map.Entry<String, Integer> firstEntry = sequencedMap.firstEntry();
Map.Entry<String, Integer> lastEntry = sequencedMap.lastEntry();

// 在映射的头部和尾部添加键值对
sequencedMap.putFirst("Zero", 0);
sequencedMap.putLast("Four", 4);

🎭 7. 未命名模式和变量 (Unnamed Patterns & Variables) - JEP 443

该特性允许你使用下划线 _ 来表示未命名的模式或变量。当你需要声明一个变量但又不需要使用它时(例如在 catch 块、try-with-resources 或模式匹配中),这可以减少冗余代码,提高可读性。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 未命名变量示例
try {
int result = someMethod();
// ... 使用 result
} catch (Exception _) {
// 我们知道可能会异常,但不关心异常对象的具体信息
System.out.println("An error occurred.");
}

try (var _ = ScopedContext.acquire()) {
// 资源需要被申请和关闭,但代码中不需要直接使用它
doWork();
}

// 未命名模式示例 (与记录模式结合)
if (obj instanceof Circle(Point(int x, _), _)) {
// 只关心圆心的 x 坐标,不关心 y 坐标和半径
System.out.println("Center X: " + x);
}

💡 其他值得注意的改进

  • 未命名类和实例 main 方法 (JEP 445 - 预览): 简化了入门程序,允许省略类声明和 main 方法的传统参数。
    1
    2
    3
    4
    5
    6
    7
    8
    // 传统的 main 方法
    void main(String[] args) {
    System.out.println("Hello, World!");
    }
    // 更简化的形式(预览)
    void main() {
    System.out.println("Hello, Simplified World!");
    }
  • 密钥封装机制 API (KEM - JEP 461): 提供了使用公钥密码加密对称密钥的标准 API。
  • 弃用和移除:
    • 出于安全考虑,弃用了 Windows 32 位 x86 端口
    • 移除了 IdentityHashMapStringBuffer 中的过时构造方法。

🚀 如何开始使用 Java 21 新特性

  1. 启用预览特性:字符串模板、未命名类和实例 main 方法等是预览功能。编译和运行时需添加参数:
    1
    2
    javac --release 21 --enable-preview MyClass.java
    java --enable-preview MyClass
    (在 IDE 中通常也有相应配置选项)
  2. 谨慎评估:对于生产环境,特别是 LTS 版本,建议充分测试新特性(尤其是预览功能)的稳定性和兼容性。

五、Java25新特性梳理

先通过一个表格快速了解 Java 25 的主要新特性:

特性类别 核心特性 状态 对开发的主要价值
语言与语法 JEP 507: 原始类型模式匹配 第三预览版 简化基本类型的类型检查和提取,代码更简洁
JEP 511: 模块导入声明 正式 简化模块依赖声明,减少重复的 import 语句
JEP 512: 紧凑源文件和实例 main 方法 正式 降低学习门槛,简化简单程序的编写
JEP 513: 灵活的构造函数体 正式 允许在构造函数中先进行参数验证再调用 super(),更安全
API 增强 JEP 506: 作用域值 (Scoped Values) 正式 轻量级且不可变的线程内数据共享,优于 ThreadLocal
JEP 505: 结构化并发 (Structured Concurrency) 第五预览版 简化多线程任务管理,提高可靠性和可观测性
JEP 508: Vector API 第十次孵化 表达向量计算,可靠编译为优化向量指令,提升计算性能
安全增强 JEP 470: 加密对象的 PEM 编码 预览 简化加密对象(密钥、证书)与 PEM 格式间的转换
JEP 510: 密钥派生函数 API 正式 支持多种算法,为量子安全加密过渡做准备
性能与监控 JEP 519: 紧凑对象头 正式 减少内存占用,提高缓存效率
JEP 514, 515: AOT 命令行易用性与方法分析 正式 简化 AOT 编译,加速应用启动
JEP 521: 分代 Shenandoah GC 正式 减少垃圾回收停顿时间,提高吞吐量
JEP 509, 518, 520: JFR 增强 实验/正式 更精确的 CPU 时间分析、协同采样、方法计时与跟踪,优化性能

🧬 语言与语法改进

Java 25 在语言层面引入了多项改进,旨在让代码更简洁、更安全,同时降低初学者的入门门槛。

  • 原始类型模式匹配(JEP 507, 第三预览版):允许在 instanceofswitch 中直接使用原始类型模式,简化代码并减少显式类型转换。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 旧写法:需要显式转换
    if (obj instanceof Integer) {
    int intValue = (Integer) obj;
    System.out.println("Integer: " + intValue);
    }

    // Java 25 新写法:直接使用原始类型模式
    if (obj instanceof int i) { // 如果 obj 是 Integer,则自动拆箱为 int 并赋值给 i
    System.out.println("It's an int: " + i);
    }
  • 模块导入声明(JEP 511, 正式):允许使用 import module 语句一次性导入一个模块导出的所有包,简化模块化开发中的依赖声明。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 一次性导入 java.sql 模块导出的所有包
    import module java.sql;

    // 但如果多个模块导出同名类,仍需显式导入特定类以避免歧义
    import java.sql.Date; // 明确指定使用 java.sql.Date

    public class Main {
    public static void main(String[] args) {
    Date sqlDate = Date.valueOf("2025-09-18"); // 使用 java.sql.Date
    }
    }
  • 紧凑源文件和实例 main 方法(JEP 512, 正式):允许省略类声明,直接使用实例 main 方法编写程序,极大降低了初学者入门门槛,也适合编写小型脚本。

    1
    2
    3
    4
    // 无需声明类!可以直接写 main 方法
    void main() {
    System.out.println("Hello, Java 25!");
    }
  • 灵活的构造函数体(JEP 513, 正式):允许在构造函数中调用 super()this() 之前执行必要的语句(如参数验证),使对象构造更安全、更自然。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Employee extends Person {
    final String name;

    Employee(String name, int age) {
    // 在调用 super() 之前进行参数验证
    if (age < 18 || age > 67) {
    throw new IllegalArgumentException("Invalid age for an employee.");
    }
    super(age); // 验证通过后再调用父类构造函数
    this.name = name;
    }
    }

🔧 API 增强

Java 25 对一些重要的 API 进行了增强和正式化。

  • 作用域值(Scoped Values, JEP 506, 正式):提供了比传统 ThreadLocal 更轻量、更高效且不可变的线程内数据共享机制,特别适合与虚拟线程和结构化并发配合使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 定义一个作用域值
    static final ScopedValue<String> CURRENT_USER = ScopedValue.newInstance();

    // 在特定作用域内绑定值并运行代码
    ScopedValue.where(CURRENT_USER, "Alice").run(() -> {
    System.out.println("Current user is: " + CURRENT_USER.get()); // 输出: Alice
    });

    // 超出作用域后,无法再获取值
    // System.out.println(CURRENT_USER.get()); // 这会抛出异常
  • 结构化并发(Structured Concurrency, JEP 505, 第五预览版):将多个相关的并发任务视为一个工作单元,简化错误处理和任务取消,提高代码的可靠性和可观测性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 结构化并发 (仍处于预览阶段)
    // try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    // Future<String> userFuture = scope.fork(() -> fetchUser());
    // Future<Integer> orderFuture = scope.fork(() -> fetchOrder());
    //
    // scope.join(); // 等待所有分支完成
    // scope.throwIfFailed(); // 如果有任何失败,抛出异常
    //
    // return new Response(userFuture.resultNow(), orderFuture.resultNow());
    // }
  • Vector API(JEP 508, 第十次孵化):允许开发者表达复杂的向量计算,这些计算会在运行时编译为优化的硬件指令,常用于人工智能推理、科学计算等场景。

🛡️ 安全增强

Java 25 在安全性方面也引入了新的 API 和支持。

  • 加密对象的 PEM 编码(JEP 470, 预览):提供了简便的 API,用于在 PEM 格式和 Java 对象之间转换加密密钥、证书等,简化与现有安全系统的集成。

    1
    2
    3
    4
    5
    6
    7
    8
    // PEM 编码 (预览功能)
    // 示例:将私钥转换为 PEM 格式 (概念性代码)
    // KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
    // KeyPair keyPair = keyGen.generateKeyPair();
    // PrivateKey privateKey = keyPair.getPrivate();
    //
    // String pemEncodedPrivateKey = PEMEncoder.encodePrivateKey(privateKey);
    // System.out.println(pemEncodedPrivateKey);
  • 密钥派生函数 API(JEP 510, 正式):为密钥派生函数(如 HKDF、Argon2)提供了标准 API,是迈向后量子密码学的重要一步。

    1
    2
    3
    4
    5
    6
    7
    8
    // 密钥派生函数 API
    // 示例:使用 HKDF 从主密钥派生新密钥 (概念性代码)
    // SecretKey masterKey = ...; // 你的主密钥
    // byte[] salt = ...;
    // byte[] info = ...;
    //
    // KeyDerivationFunction kdf = KeyDerivationFunction.getInstance("HKDF");
    // SecretKey derivedKey = kdf.deriveKey(masterKey, 256, salt, info);

⚡ 性能与监控改进

Java 25 在运行时性能、内存效率和监控方面也带来了多项优化。

  • 紧凑对象头(JEP 519, 正式):在 64 位平台上将对象头大小从 128 位压缩至 64 位,减少了内存占用,并可能提高缓存效率。

  • AOT(提前编译)增强(JEP 514, 515, 正式):改进了 AOT 编译的命令行易用性,并支持通过方法分析信息生成更优的本地代码,加速应用启动速度

  • 分代 Shenandoah 垃圾收集器(JEP 521, 正式):Shenandoah GC 现在正式支持分代模式,通过更频繁地收集年轻代对象,进一步降低停顿时间,提高吞吐量。

  • JFR(JDK Flight Recorder)增强

    • JFR CPU 时间分析(JEP 509, 实验性):在 Linux 上提供更精确的 CPU 时间分析信息,帮助定位性能瓶颈。
    • JFR 协同采样(JEP 518, 正式):改进异步采样堆栈跟踪的稳定性,减少性能开销。
    • JFR 方法计时与跟踪(JEP 520, 正式):通过字节码增强支持方法级别的时间分析和跟踪,便于深度性能剖析。

🧪 其他重要更新

  • 稳定值(Stable Values, JEP 502, 预览):引入了“稳定值”的概念,允许不可变数据对象享受与 final 字段类似的性能优化(如常量折叠),但在初始化时机上又具有灵活性。
  • 移除 32 位 x86 端口(JEP 503):移除了对 32 位 x86 架构的支持,以集中资源维护更现代的平台。

🚀 如何尝试 Java 25

  1. 下载 JDK:从 Oracle 官网Adoptium 等渠道下载 JDK 25。
  2. 在 IDE 中配置:以 IntelliJ IDEA 为例,在 Project Structure 中下载或指定 JDK 25,并将语言级别设置为 25(对于预览特性,需设置为 25 (Preview))。
  3. 使用预览特性:要使用预览特性,在编译和运行时需显式添加参数:
    1
    2
    javac --release 25 --enable-preview YourClass.java
    java --enable-preview YourClass

💎 总结与建议

Java 25 作为最新的 LTS 版本,带来了许多令人兴奋的改进:

  • 对初学者更友好:紧凑的源文件和实例 main 方法显著降低了学习门槛。
  • 并发编程模型现代化:作用域值(正式)和结构化并发(预览)与虚拟线程配合,为高并发应用提供了更强大的工具。
  • 性能持续提升:紧凑对象头、分代 Shenandoah GC 和 AOT 改进等,从不同角度优化了性能。
  • 安全性增强:新的 PEM 编码和 KDF API 适应了现代安全需求。
  • 语言表达力更强:原始类型模式匹配、灵活的构造函数等让代码更简洁安全。

给你的建议

  • 对于新项目,特别是微服务、云原生或涉及大量 I/O 并发及AI计算的场景,可以考虑直接从 Java 25 LTS 开始。
  • 对于现有项目,升级前务必充分测试,尤其是注意依赖库的兼容性。可优先评估作用域值、灵活构造函数等特性带来的好处。
  • 对于初学者,JEP 512 使得入门 Java 变得更加简单。