正式特性
【必备】外部函数和内存 API
长期以来,Java 程序员想要调用 C/C++ 编写的本地库,只能依赖 JNI(Java Native Interface)。但说实话,JNI 的使用体验并不好,需要手写胶水代码、维护头文件和构建脚本、处理 JNIEnv 和复杂类型转换,一旦接口频繁变更,维护成本较高。
外部函数与内存 API(FFM API)提供了标准化、类型安全的方式来从 Java 直接调用本地代码,并在 Java 侧描述函数签名和内存布局。比起 JNI,FFM 一般不需要编写 JNI 胶水代码,调用链更简洁、可维护性更好。
让我们看看用 FFM API 是怎么做的:
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
public class ModernNativeCall {
public static void main(String[] args) throws Throwable {
// 获取本地链接器和符号查找器
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
// 直接调用系统已有的C标准库函数,无需自己编写C代码
MethodHandle sqrt = linker.downcallHandle(
stdlib.find("sqrt").orElseThrow(() ->
new RuntimeException("sqrt函数未找到")),
FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE)
);
double result = (double) sqrt.invoke(16.0);
System.out.println("sqrt(16) = " + result);
// 如果要调用自定义库,也只需要一个Java文件
try (Arena arena = Arena.ofConfined()) {
SymbolLookup customLib = SymbolLookup.libraryLookup("mycalculator", arena);
MethodHandle add = linker.downcallHandle(
customLib.find("add").orElseThrow(() ->
new RuntimeException("add函数未找到")),
FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.JAVA_INT,
ValueLayout.JAVA_INT)
);
int sum = (int) add.invoke(10, 20);
System.out.println("10 + 20 = " + sum);
} // 库资源在这里自动释放
}
}这里有几个关键概念需要理解:
- Linker 负责连接 Java 代码和本地代码
- SymbolLookup 用来查找本地库中的函数
- MethodHandle 是 Java 中已有的概念,表示本地函数的调用
要特别注意的是 Arena.ofConfined(),这是 FFM API 中一个非常重要的内存管理概念。Arena 可以理解为一个内存管理区域,它控制着内存的生命周期。当你使用 try-with-resources 语句时,Arena 会在代码块结束时自动释放所有相关的内存资源,这样就避免了内存泄漏的问题。
FFM API 还有一个强大的特性是 支持双向调用。不仅可以从 Java 调用 C 函数,还可以把 Java 方法作为函数指针传递给 C 函数。比如你可以用 Java 写一个比较器,然后传给 C 标准库的 qsort 函数:
public class QsortExample {
// Java比较器方法
static int qsortCompare(MemorySegment elem1, MemorySegment elem2) {
return Integer.compare(
elem1.get(ValueLayout.JAVA_INT, 0),
elem2.get(ValueLayout.JAVA_INT, 0)
);
}
public static void main(String[] args) throws Throwable {
Linker linker = Linker.nativeLinker();
// 找到qsort函数
MethodHandle qsort = linker.downcallHandle(
linker.defaultLookup().find("qsort").orElseThrow(),
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS,
ValueLayout.JAVA_LONG,
ValueLayout.JAVA_LONG,
ValueLayout.ADDRESS)
);
// 把Java方法包装成函数指针
MethodHandle comparHandle = MethodHandles.lookup()
.findStatic(QsortExample.class, "qsortCompare",
MethodType.methodType(int.class,
MemorySegment.class,
MemorySegment.class));
MemorySegment comparFunc = linker.upcallStub(
comparHandle,
FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT),
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT)),
Arena.ofAuto()
);
// 使用qsort排序数组
try (Arena arena = Arena.ofConfined()) {
MemorySegment array = arena.allocateFrom(
ValueLayout.JAVA_INT, 0, 9, 3, 4, 6, 5, 1, 8, 2, 7);
qsort.invoke(array, 10L, ValueLayout.JAVA_INT.byteSize(), comparFunc);
int[] sorted = array.toArray(ValueLayout.JAVA_INT);
System.out.println(java.util.Arrays.toString(sorted));
// 输出: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
}
}FFM API 现在支持几乎所有主流平台,性能相比 JNI 可能有一定提升,特别是在频繁调用本地函数的场景下。
内存管理示例
import java.lang.foreign.*;
public class MemoryManagementExample {
public static void main(String[] args) {
// 使用 Arena 管理内存
try (Arena arena = Arena.ofConfined()) {
// 分配本地内存
MemorySegment segment = arena.allocate(1024);
// 写入数据
segment.setAtIndex(ValueLayout.JAVA_INT, 0, 42);
segment.setAtIndex(ValueLayout.JAVA_INT, 1, 24);
// 读取数据
int value1 = segment.getAtIndex(ValueLayout.JAVA_INT, 0);
int value2 = segment.getAtIndex(ValueLayout.JAVA_INT, 1);
System.out.println("值1: " + value1 + ", 值2: " + value2);
// 分配字符串
MemorySegment cString = arena.allocateFrom("Hello FFM API!");
System.out.println("C字符串长度: " + cString.byteSize());
} // Arena 自动释放所有内存
}
}【了解】未命名变量和模式
在开发中,我们可能会遇到这样的情况:有些变量我们必须声明,但实际上并不会使用到它们的值。
在这之前,我们只能给这些不使用的变量起一个名字,代码会显得有些多余。举些例子:
try {
processData();
} catch (IOException ignored) { // 只关心异常发生,不关心异常对象
System.out.println("处理数据时出错了");
}
String result = switch (obj) {
case Integer i -> "这是整数: " + i;
case String s -> "这是字符串: " + s;
case Double unused -> "这是浮点数"; // 不需要使用具体的值
default -> "未知类型";
};有了未命名变量特性,可以使用下划线 _ 表示不使用的变量代码,意图更清晰:
try {
processData();
} catch (IOException _) { // 不关心异常对象
System.out.println("处理数据时出错了");
}
String result = switch (obj) {
case Integer i -> "这是整数: " + i;
case String s -> "这是字符串: " + s;
case Double _ -> "这是浮点数"; // 只关心类型,不关心值
default -> "未知类型";
};
// 在解构中也很有用
if (point instanceof Point(var x, var _)) { // 只关心 x 坐标
System.out.println("x 坐标是: " + x);
}未命名变量的使用场景
public class UnnamedVariableExample {
public static void main(String[] args) {
// 在 for 循环中
for (int i = 0, _ = sideEffect(); i < 10; i++) {
System.out.println("循环 " + i);
}
// 在多重赋值中
var (result, _) = processData(); // 只关心第一个返回值
// 在 lambda 表达式中
list.forEach(_ -> System.out.println("处理一个元素"));
// 在 try-with-resources 中
try (var _ = acquireResource()) {
// 只需要资源被正确关闭,不关心资源对象本身
doWork();
}
}
private static int sideEffect() {
System.out.println("副作用");
return 42;
}
private static String processData() {
return "result";
}
private static AutoCloseable acquireResource() {
return () -> System.out.println("资源已关闭");
}
private static void doWork() {
System.out.println("执行工作");
}
}【了解】G1 的区域固定
Java 22 改进了 G1 垃圾收集器,引入了区域固定功能:
# 启用 G1 垃圾收集器
java -XX:+UseG1GC MyApp
# G1 现在可以固定某些区域,避免在 GC 期间移动
# 这对于与本地代码交互的应用特别有用这个改进主要影响:
- 本地内存交互:减少 GC 对本地代码的影响
- 大对象处理:改善大对象的 GC 性能
- 延迟优化:减少某些 GC 暂停
【了解】启动多文件源代码程序
Java 22 扩展了单文件源代码程序的功能,现在支持多文件:
# 可以直接运行多文件的 Java 程序
java Main.java
# 或者指定主类
java --main-class com.example.Main *.java这个特性让原型开发和脚本编写更加方便:
// Main.java
import com.example.Utils;
public class Main {
public static void main(String[] args) {
Utils.printMessage("Hello Multi-file!");
}
}
// com/example/Utils.java
package com.example;
public class Utils {
public static void printMessage(String message) {
System.out.println("消息: " + message);
}
}预览特性
【了解】super(...) 之前的语句(预览)
Java 22 允许在调用 super(...) 之前执行一些语句:
// 需要启用预览功能
// javac --enable-preview --release 22 Example.java
public class Child extends Parent {
private final String processedValue;
public Child(String rawValue) {
// Java 22 之前,这里不能有任何语句
String processed = processRawValue(rawValue); // 现在可以了!
super(processed);
this.processedValue = processed;
}
private static String processRawValue(String raw) {
return raw.toUpperCase().trim();
}
}
class Parent {
private final String value;
public Parent(String value) {
this.value = value;
}
}这个特性解决了构造函数中参数预处理的问题:
public class DatabaseConnection extends Connection {
public DatabaseConnection(String url, Properties props) {
// 预处理配置
Properties processedProps = new Properties(props);
processedProps.setProperty("useSSL", "true");
// 验证 URL
if (!isValidUrl(url)) {
throw new IllegalArgumentException("无效的数据库 URL");
}
String normalizedUrl = normalizeUrl(url);
// 现在调用父类构造函数
super(normalizedUrl, processedProps);
}
private static boolean isValidUrl(String url) {
return url != null && url.startsWith("jdbc:");
}
private static String normalizeUrl(String url) {
return url.toLowerCase();
}
}【了解】类文件 API(预览)
Java 22 引入了类文件 API 作为预览特性,提供了读取、分析和生成 Java 类文件的标准方法:
import java.lang.classfile.*;
import java.lang.constant.ClassDesc;
// 需要启用预览功能
public class ClassFileExample {
public static void main(String[] args) throws Exception {
// 读取现有的类文件
ClassModel classModel = ClassFile.of().parse(
ClassFileExample.class.getResourceAsStream("Example.class").readAllBytes()
);
// 分析类信息
System.out.println("类名: " + classModel.thisClass().asInternalName());
System.out.println("父类: " + classModel.superclass().orElse(null));
// 列出方法
classModel.methods().forEach(method -> {
System.out.println("方法: " + method.methodName().stringValue());
});
// 生成新的类文件
byte[] newClassBytes = ClassFile.of().build(
ClassDesc.of("com.example.Generated"),
classBuilder -> {
classBuilder.withMethod(
"hello",
MethodTypeDesc.of(ConstantDescs.CD_void),
ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
methodBuilder -> {
methodBuilder.withCode(codeBuilder -> {
codeBuilder.getstatic(
ClassDesc.of("java.lang.System"),
"out",
ClassDesc.of("java.io.PrintStream")
);
codeBuilder.ldc("Hello from generated class!");
codeBuilder.invokevirtual(
ClassDesc.of("java.io.PrintStream"),
"println",
MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_String)
);
codeBuilder.return_();
});
}
);
}
);
// 保存生成的类文件
java.nio.file.Files.write(
java.nio.file.Paths.get("Generated.class"),
newClassBytes
);
}
}【了解】字符串模板(第二次预览)
Java 22 继续完善字符串模板特性:
// 需要启用预览功能
public class StringTemplateExample {
public static void main(String[] args) {
String name = "张三";
int age = 25;
double salary = 15000.50;
// 基本字符串模板
String basic = STR."姓名: \{name}, 年龄: \{age}";
System.out.println(basic);
// 格式化模板
String formatted = FMT."姓名: %-10s\{name} 年龄: %3d\{age} 工资: %,.2f\{salary}";
System.out.println(formatted);
// 自定义模板处理器
String json = JSON."""
{
"name": "\{name}",
"age": \{age},
"salary": \{salary}
}
""";
System.out.println(json);
// 多行模板
String html = STR."""
<html>
<body>
<h1>员工信息</h1>
<p>姓名: \{name}</p>
<p>年龄: \{age}</p>
<p>工资: \{salary}</p>
</body>
</html>
""";
System.out.println(html);
}
// 自定义模板处理器
public static final StringTemplate.Processor<String, RuntimeException> JSON =
StringTemplate.Processor.of((StringTemplate st) -> {
StringBuilder sb = new StringBuilder();
Iterator<String> fragments = st.fragments().iterator();
Iterator<Object> values = st.values().iterator();
while (fragments.hasNext()) {
sb.append(fragments.next());
if (values.hasNext()) {
Object value = values.next();
if (value instanceof String) {
sb.append('"').append(value).append('"');
} else {
sb.append(value);
}
}
}
return sb.toString();
});
}【了解】Stream Gatherers(预览)
Java 22 引入了 Stream Gatherers 作为预览特性:
import java.util.stream.Gatherer;
import java.util.stream.Gatherers;
// 需要启用预览功能
public class GatherersExample {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 使用内置的 Gatherers
// 1. 滑动窗口
List<List<Integer>> windows = numbers.stream()
.gather(Gatherers.windowSliding(3))
.toList();
System.out.println("滑动窗口: " + windows);
// [[1, 2, 3], [2, 3, 4], [3, 4, 5], ...]
// 2. 固定窗口
List<List<Integer>> fixedWindows = numbers.stream()
.gather(Gatherers.windowFixed(3))
.toList();
System.out.println("固定窗口: " + fixedWindows);
// [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
// 3. 扫描(累积操作)
List<Integer> scan = numbers.stream()
.gather(Gatherers.scan(() -> 0, Integer::sum))
.toList();
System.out.println("扫描求和: " + scan);
// [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
// 自定义 Gatherer
Gatherer<String, ?, String> addPrefix = Gatherer.ofSequential(
() -> null, // 初始状态
(state, element, downstream) -> {
downstream.push("前缀-" + element);
return true;
}
);
List<String> words = List.of("apple", "banana", "cherry");
List<String> prefixed = words.stream()
.gather(addPrefix)
.toList();
System.out.println("添加前缀: " + prefixed);
// [前缀-apple, 前缀-banana, 前缀-cherry]
}
}【了解】结构化并发(第二次预览)
Java 22 继续完善结构化并发:
import jdk.incubator.concurrent.StructuredTaskScope;
// 需要启用预览功能和孵化器模块
public class StructuredConcurrencyExample {
public static void main(String[] args) {
UserProfile profile = fetchUserProfile(123);
System.out.println(profile);
}
record UserProfile(User user, Settings settings, List<Notification> notifications) {}
record User(String name, String email) {}
record Settings(String theme, String language) {}
record Notification(String message, String type) {}
public static UserProfile fetchUserProfile(int userId) {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 并发执行多个任务
var userTask = scope.fork(() -> fetchUser(userId));
var settingsTask = scope.fork(() -> fetchSettings(userId));
var notificationsTask = scope.fork(() -> fetchNotifications(userId));
// 等待所有任务完成
scope.join();
scope.throwIfFailed();
// 组合结果
return new UserProfile(
userTask.resultNow(),
settingsTask.resultNow(),
notificationsTask.resultNow()
);
} catch (Exception e) {
throw new RuntimeException("获取用户资料失败", e);
}
}
private static User fetchUser(int userId) {
// 模拟数据库查询
simulateDelay(100);
return new User("用户" + userId, "user" + userId + "@example.com");
}
private static Settings fetchSettings(int userId) {
simulateDelay(150);
return new Settings("dark", "zh-CN");
}
private static List<Notification> fetchNotifications(int userId) {
simulateDelay(80);
return List.of(
new Notification("欢迎回来!", "info"),
new Notification("您有新消息", "alert")
);
}
private static void simulateDelay(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}孵化器特性
【了解】向量 API(第七次孵化器)
Java 22 继续改进向量 API:
import jdk.incubator.vector.*;
public class VectorAPIExample {
private static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
public static void main(String[] args) {
// 向量化的数组操作
float[] a = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
float[] b = {8.0f, 7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f};
float[] result = new float[a.length];
// 向量化加法
vectorAdd(a, b, result);
System.out.println("向量加法: " + Arrays.toString(result));
// 向量化点积
float dotProduct = vectorDotProduct(a, b);
System.out.println("点积: " + dotProduct);
}
public static void vectorAdd(float[] a, float[] b, float[] result) {
int upperBound = SPECIES.loopBound(a.length);
// 向量化循环
for (int i = 0; i < upperBound; i += SPECIES.length()) {
FloatVector va = FloatVector.fromArray(SPECIES, a, i);
FloatVector vb = FloatVector.fromArray(SPECIES, b, i);
FloatVector vr = va.add(vb);
vr.intoArray(result, i);
}
// 处理剩余元素
for (int i = upperBound; i < a.length; i++) {
result[i] = a[i] + b[i];
}
}
public static float vectorDotProduct(float[] a, float[] b) {
FloatVector sum = FloatVector.zero(SPECIES);
int upperBound = SPECIES.loopBound(a.length);
for (int i = 0; i < upperBound; i += SPECIES.length()) {
FloatVector va = FloatVector.fromArray(SPECIES, a, i);
FloatVector vb = FloatVector.fromArray(SPECIES, b, i);
sum = va.fma(vb, sum);
}
float result = sum.reduceLanes(VectorOperators.ADD);
// 处理剩余元素
for (int i = upperBound; i < a.length; i++) {
result += a[i] * b[i];
}
return result;
}
}总结
Java 22 最重要的特性是外部函数和内存 API 的正式化,这为 Java 与本地代码的交互提供了现代化的解决方案。未命名变量和模式让代码更加简洁和表达力更强。
虽然大部分新特性还处于预览阶段,但字符串模板、Stream Gatherers 等特性都展现出了巨大的潜力。类文件 API 为字节码操作提供了标准化的方法,结构化并发继续完善 Java 的并发编程模型。
Java 22 虽然不是 LTS 版本,但它为未来的重要特性奠定了基础,特别是在性能优化和开发体验方面做出了重要贡献。