JDK 22 新特性详解

JDK 22 新特性详解

发布日期 :2024 年 3 月 19 日

规范版本 :Java SE 22(JSR 397)

是否 LTS :否

主题:外部函数与内存 API 终于转正、Unnamed Variables、Stream Gatherers 首次预览


一、版本总览

JDK 22 共集成 12 个 JEP,其中两个正式特性:

JEP 特性 状态
454 Foreign Function & Memory API 正式(历经 8 轮孵化/预览)
456 Unnamed Variables & Patterns 正式
458 Launch Multi-File Source-Code Programs 正式
423 Region Pinning for G1 正式
461 Stream Gatherers 预览
462 Structured Concurrency 第二次预览
463 Implicitly Declared Classes and Instance Main Methods 第二次预览
464 Scoped Values 第二次预览
457 Class-File API 预览
459 String Templates 第二次预览(后被搁置)
460 Vector API 第七次孵化

二、正式特性

1. JEP 454:外部函数与内存 API(正式)

这是 JDK 22 最重要的特性。历经:

版本 JEP 阶段
16 389 + 393 两个独立孵化 API
17 412 合并孵化
18 419 第二次孵化
19 424 预览
20 434 第二次预览
21 442 第三次预览
22 454 正式

替代什么:

  • JNI(复杂、易错、需要 C 代码与编译步骤)
  • sun.misc.Unsafe(不安全、非标准)
  • ByteBuffer(限制多:2GB 上限、无手动释放)

核心概念:

概念 说明
Arena 内存生命周期管理(此前叫 MemorySession / ResourceScope)
MemorySegment 一段连续内存(可以是堆内或堆外)
MemoryLayout 内存布局描述(用于结构化访问)
ValueLayout 基础类型的布局(JAVA_INT、JAVA_LONG 等)
Linker 本地函数链接器(downcall / upcall)
SymbolLookup 符号查找(查找本地函数地址)
FunctionDescriptor 函数签名描述

Arena 生命周期:

java 复制代码
import java.lang.foreign.*;

// ① 受限 Arena:单线程访问,确定性释放
try (Arena arena = Arena.ofConfined()) {
    MemorySegment seg = arena.allocate(100);
    // 使用 seg
}   // 自动释放

// ② 共享 Arena:多线程访问
try (Arena arena = Arena.ofShared()) {
    MemorySegment seg = arena.allocate(100);
    // 多线程可访问
}

// ③ 自动 Arena:由 GC 管理释放(时间不确定)
Arena arena = Arena.ofAuto();
MemorySegment seg = arena.allocate(100);

// ④ 全局 Arena:永不释放(用于长期存活的数据)
Arena global = Arena.global();
MemorySegment seg = global.allocate(100);

堆外内存分配与访问:

java 复制代码
try (Arena arena = Arena.ofConfined()) {
    // 分配 1KB 堆外内存
    MemorySegment segment = arena.allocate(1024);

    // 使用 ValueLayout 读写
    segment.set(ValueLayout.JAVA_INT, 0, 42);
    segment.set(ValueLayout.JAVA_LONG, 8, 123456789L);
    segment.set(ValueLayout.JAVA_DOUBLE, 16, 3.14159);

    int i = segment.get(ValueLayout.JAVA_INT, 0);
    long l = segment.get(ValueLayout.JAVA_LONG, 8);
    double d = segment.get(ValueLayout.JAVA_DOUBLE, 16);

    // 分配 UTF-8 字符串
    MemorySegment str = arena.allocateFrom("Hello, FFM!");
    System.out.println(str.getString(0));   // "Hello, FFM!"

    // 分配数组
    MemorySegment intArray = arena.allocateFrom(ValueLayout.JAVA_INT, 1, 2, 3, 4, 5);
    intArray.getAtIndex(ValueLayout.JAVA_INT, 2);   // 3
}

结构化布局(Struct Layout):

java 复制代码
// 定义 C 结构体:struct Point { int x; int y; }
StructLayout pointLayout = MemoryLayout.structLayout(
        ValueLayout.JAVA_INT.withName("x"),
        ValueLayout.JAVA_INT.withName("y")
);

// 创建 VarHandle 用于字段访问
VarHandle xHandle = pointLayout.varHandle(
        MemoryLayout.PathElement.groupElement("x"));
VarHandle yHandle = pointLayout.varHandle(
        MemoryLayout.PathElement.groupElement("y"));

try (Arena arena = Arena.ofConfined()) {
    MemorySegment point = arena.allocate(pointLayout);
    xHandle.set(point, 0L, 10);     // 注意:访问时需提供偏移量
    yHandle.set(point, 0L, 20);

    System.out.println(xHandle.get(point, 0L));   // 10
    System.out.println(yHandle.get(point, 0L));   // 20
}

数组布局:

java 复制代码
// 定义 int[10]
SequenceLayout intArrayLayout = MemoryLayout.sequenceLayout(10, ValueLayout.JAVA_INT);

VarHandle elemHandle = intArrayLayout.varHandle(
        MemoryLayout.PathElement.sequenceElement());

try (Arena arena = Arena.ofConfined()) {
    MemorySegment arr = arena.allocate(intArrayLayout);
    for (int i = 0; i < 10; i++) {
        elemHandle.set(arr, 0L, (long) i, i * i);
    }
    System.out.println(elemHandle.get(arr, 0L, 5L));   // 25
}

调用 C 函数(downcall):

java 复制代码
import java.lang.invoke.MethodHandle;
import java.lang.foreign.*;

Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();

// ① 调用 strlen
MethodHandle strlen = linker.downcallHandle(
        stdlib.find("strlen").orElseThrow(),
        FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);

try (Arena arena = Arena.ofConfined()) {
    MemorySegment cString = arena.allocateFrom("Hello, World!");
    long len = (long) strlen.invokeExact(cString);
    System.out.println("length = " + len);   // 13
}

// ② 调用 qsort
MethodHandle qsort = linker.downcallHandle(
        stdlib.find("qsort").orElseThrow(),
        FunctionDescriptor.ofVoid(
                ValueLayout.ADDRESS,                          // base
                ValueLayout.JAVA_LONG,                        // nmemb
                ValueLayout.JAVA_LONG,                        // size
                ValueLayout.ADDRESS                           // compar
        )
);

// ③ 使用 GetSystemTime 之类的平台 API

把 Java 方法作为回调传给 C(upcall):

java 复制代码
// 定义比较函数
static int compareInts(MemorySegment a, MemorySegment b) {
    int x = a.get(ValueLayout.JAVA_INT, 0);
    int y = b.get(ValueLayout.JAVA_INT, 0);
    return Integer.compare(x, y);
}

// 生成函数指针(stub)
MethodHandle compareHandle = MethodHandles.lookup().findStatic(
        Main.class, "compareInts",
        MethodType.methodType(int.class, MemorySegment.class, MemorySegment.class));

MemorySegment compareStub = linker.upcallStub(
        compareHandle,
        FunctionDescriptor.of(ValueLayout.JAVA_INT,
                ValueLayout.ADDRESS, ValueLayout.ADDRESS),
        arena);

// 用作 qsort 的比较函数
qsort.invokeExact(intArray, 5L, 4L, compareStub);

访问本地内存的指针:

java 复制代码
// 从 C 返回的指针创建 MemorySegment
MemorySegment ptr = (MemorySegment) malloc.invokeExact(1024L);
// 指针的 reinterpret 需要指定大小
MemorySegment bounded = ptr.reinterpret(1024);

与 JNI 的对比:

维度 JNI FFM API
语言 需要 C 代码 纯 Java
编译步骤 需要 gcc 等 不需要
性能 较高 较高(接近 JNI,JDK 22 起甚至有优化超越)
安全性 低(易崩溃) 较高(边界检查、生命周期管理)
可维护性 差 好
跨平台 需分别为每个平台编译 一份 Java 代码
推荐度 仅遗留代码 新项目首选

迁移建议:

  • 新项目直接使用 FFM API
  • 已有 JNI 代码可逐步迁移,重点是简单包装函数
  • 用 --enable-native-access=模块名 控制本地访问权限(JDK 24 起会警告,JEP 472)

访问限制(JDK 22 中仍宽松,后续收紧):

bash 复制代码
# JDK 22:本地访问不产生警告
java MyApp

# JDK 24 起(JEP 472):会打印警告
# WARNING: A restricted method in java.lang.foreign.Linker has been called
# WARNING: Restricted methods will be blocked in a future release
# 需显式授权:
java --enable-native-access=ALL-UNNAMED MyApp

2. JEP 456:Unnamed Variables & Patterns(正式)

从 JDK 21(JEP 443)预览转正。

java 复制代码
// ① 未使用的 catch 参数
try {
    Integer.parseInt(input);
} catch (NumberFormatException _) {
    System.out.println("not a number");
}

// ② 未使用的 lambda 参数
map.forEach((_, value) -> System.out.println(value));

// ③ record pattern 中忽略组件
record Point(int x, int y) { }
if (obj instanceof Point(int x, _)) {
    System.out.println("x = " + x);
}

// ④ switch 中忽略类型
switch (obj) {
    case Point(int x, _)    -> "x = " + x;
    case String _           -> "some string";
    case Integer _          -> "some int";
    default                 -> "other";
}

// ⑤ 未使用的局部变量(有副作用但不需要结果)
int _ = computeWithSideEffect();

// ⑥ 未使用的 for 循环变量
for (Order _ : orders) {
    count++;
}

// ⑦ try-with-resources
try (var _ = openConnection()) {
    doWork();
}

// ⑧ 未使用的嵌套 record pattern 组件
if (obj instanceof Line(Point(_, int y1), Point(_, int y2))) {
    System.out.println(y1 + ", " + y2);
}

规则:

  • _ 是关键字(JDK 21 起),不能作为标识符
  • 同一作用域内可有多个 _(因为都没有名字)
  • _ 不能用于「读取」(它没有名字)
  • 匿名类不能声明 _ 字段

价值:显式表达「我故意不使用这个变量」,让代码意图更清晰,IDE 与静态分析工具不再报警告。

示例:更清晰的代码

java 复制代码
// 之前:需要为不使用的变量起名
try {
    ...
} catch (Exception ex) {          // ex 未被使用,IDE 警告
    logger.warn("operation failed");
}

// JDK 22:意图明确
try {
    ...
} catch (Exception _) {          // 明确表示不使用
    logger.warn("operation failed");
}

3. JEP 458:启动多文件源代码程序

在 JDK 11(JEP 330)单文件源码启动的基础上,支持多文件。

bash 复制代码
# 有多个源文件时,直接运行主文件
java Main.java
# 编译器会自动编译 Main.java 引用的其它同目录源文件

示例:

text 复制代码
project/
├── Main.java
├── Greeter.java
└── util/
    └── Helper.java
java 复制代码
// Main.java
public class Main {
    public static void main(String[] args) {
        Greeter greeter = new Greeter();
        System.out.println(greeter.greet("World"));
        System.out.println(Helper.uppercase("hello"));
    }
}
java 复制代码
// Greeter.java
public class Greeter {
    String greet(String name) {
        return "Hello, " + name + "!";
    }
}
java 复制代码
// util/Helper.java
package util;

public class Helper {
    public static String uppercase(String s) {
        return s.toUpperCase();
    }
}
bash 复制代码
$ java Main.java
Hello, World!
HELLO

重要规则:

规则 说明
入口类 第一个源文件中的第一个顶层类
自动编译 同目录及子目录下被引用的源文件
搜索范围 源文件的包声明 决定搜索路径(util/Helper.java 对应 package util)
不产生 .class 编译在内存中进行
不支持模块 源文件不能是模块化程序
<h1>性能</h1> 每次运行都重新编译,不适合反复运行的大项目

对脚本化的价值:

bash 复制代码
#!/usr/bin/java --source 22

// 可直接执行的 Java 脚本(配合 shebang)
public class Script {
    public static void main(String[] args) {
        System.out.println("Running as a script: " + String.join(",", args));
    }
}

与 jshell 的区别:

工具 用途
jshell 交互式 REPL(逐行输入)
java Xxx.java 运行源文件(一次性执行)
javac + java 传统编译运行

4. JEP 423:Region Pinning for G1

背景 :JNI 临界区(critical region) 中的代码会直接访问对象地址(GetPrimitiveArrayCritical / GetStringCritical)。在此期间,GC 不能移动这些对象。

问题 :G1 此前在 JNI 临界区期间会禁用整个 GC,导致在这些区域长期持有数组(如大数据传输)时,GC 停顿时间不可控。

变更 :G1 现在只固定(pin)包含这些对象的区域(Region),而不是禁用整个 GC。

bash 复制代码
# 无需额外参数,默认启用
java -XX:+UseG1GC MyApp

收益:

  • JNI 临界区期间 GC 仍可运行(除了被固定的区域)
  • 显著降低 JNI 密集应用的 GC 停顿
  • 减少「GC 停顿尖峰」

5. JEP 457:Class-File API(预览)

目标 :提供标准的 Java API 来解析、生成、转换 class 文件,替代第三方库(ASM、Byte Buddy 的核心部分)。

为什么需要:

  • JVM 每个版本都会新增 class 文件的特性(新的常量、属性、指令)
  • JDK 自身(javac、JFR、反射、Lambda 元工厂)都内部使用 ASM,但 ASM 更新滞后于 JDK
  • 第三方框架(Kotlin、Groovy、JaCoCo、Mockito、Spring)都需要维护自己的 class 文件解析器

提供的能力:

java 复制代码
import java.lang.classfile.*;
import java.lang.classfile.instruction.*;

// ① 解析 class 文件
ClassFile cf = ClassFile.of();
ClassModel classModel = cf.parse(Path.of("MyClass.class"));

System.out.println("class: " + classModel.thisClass().asInternalName());
for (MethodModel method : classModel.methods()) {
    System.out.println("  method: " + method.methodName().stringValue()
            + method.methodType().stringValue());
}

// ② 生成 class 文件
byte[] bytes = cf.build(ClassDesc.of("com.example.Hello"), classBuilder -> {
    classBuilder.withFlags(ClassFile.ACC_PUBLIC);
    classBuilder.withMethodBody("main",
            MethodTypeDesc.of(ConstantDescs.CD_void,
                    ClassDesc.of("java.lang.String").arrayType()),
            ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
            codeBuilder -> {
                codeBuilder.getstatic(ClassDesc.of("java.lang.System"),
                        "out", ClassDesc.of("java.io.PrintStream"));
                codeBuilder.ldc("Hello, Class-File API!");
                codeBuilder.invokevirtual(ClassDesc.of("java.io.PrintStream"),
                        "println", MethodTypeDesc.of(ConstantDescs.CD_void,
                                ClassDesc.of("java.lang.String")));
                codeBuilder.return_();
            });
});

// ③ 转换 class 文件
byte[] transformed = cf.transformClass(ClassModel.of(bytes), (classBuilder, element) -> {
    if (element instanceof MethodModel method) {
        // 修改方法
        classBuilder.withMethodBody(method.methodName(), method.methodType(),
                method.flags().flagsMask(), codeBuilder -> {
                    // ... 重新生成方法体
                });
    } else {
        classBuilder.with(element);
    }
});

核心 API:

接口/类 说明
ClassFile 入口(parse、build、transformClass)
ClassModel 类模型(methods()、fields()、thisClass())
MethodModel 方法模型
FieldModel 字段模型
CodeModel 方法体模型
CodeBuilder 方法体构建器
ClassBuilder 类构建器
ClassElement 类元素的 sealed 接口(MethodModel、FieldModel 等均实现)

演进路径:

版本 JEP 状态
22 457 预览
23 466 第二次预览
24 484 正式

6. JEP 461:Stream Gatherers(预览)

目标 :为 Stream API 提供可扩展的中间操作机制。

问题 :Stream 的中间操作是固定的 (filter、map、flatMap、distinct、sorted、limit、takeWhile 等)。部分常见需求没有对应的内置操作:

java 复制代码
// 需求 1:按字符串长度去重(而非按内容)
var result = Stream.of("foo", "bar", "baz", "quux")
        .distinctBy(String::length)      // ❌ 不存在
        .toList();
// 期望:[foo, quux]

// 需求 2:固定窗口分组
var windows = Stream.iterate(0, i -> i + 1)
        .windowFixed(3)                  // ❌ 不存在
        .limit(2)
        .toList();
// 期望:[[0,1,2], [3,4,5]]

// 需求 3:累积扫描
List<Integer> prefixSums = Stream.of(1, 2, 3, 4)
        .scan((a, b) -> a + b)           // ❌ 不存在
        .toList();
// 期望:[1, 3, 6, 10]

Stream::gather(Gatherer) ------对应 Stream::collect(Collector) 的地位:

java 复制代码
// 使用内置 Gatherers
import java.util.stream.Gatherers;

// ① windowFixed:固定大小窗口
List<List<Integer>> windows = Stream.iterate(0, i -> i + 1)
        .gather(Gatherers.windowFixed(3))
        .limit(2)
        .toList();
// [[0, 1, 2], [3, 4, 5]]

// ② windowSliding:滑动窗口
List<List<Integer>> slides = Stream.of(1, 2, 3, 4, 5)
        .gather(Gatherers.windowSliding(3))
        .toList();
// [[1,2,3], [2,3,4], [3,4,5]]

// ③ fold:累积折叠为单个结果
Optional<String> joined = Stream.of("a", "b", "c")
        .gather(Gatherers.fold(() -> "", (acc, s) -> acc + s))
        .findFirst();
// "abc"

// ④ scan:累积扫描
List<Integer> prefixSums = Stream.of(1, 2, 3, 4)
        .gather(Gatherers.scan(() -> 0, Integer::sum))
        .toList();
// [1, 3, 6, 10]

// ⑤ mapConcurrent:并发映射(虚拟线程)
List<String> results = urls.stream()
        .gather(Gatherers.mapConcurrent(16, url -> fetch(url)))
        .toList();

自定义 Gatherer:

java 复制代码
// 实现「按长度去重」
static <T, K> Gatherer<T, ?, T> distinctBy(Function<? super T, K> keyExtractor) {
    return Gatherer.ofSequential(
            HashSet::new,                       // 初始状态
            (set, element, downstream) -> {
                K key = keyExtractor.apply(element);
                if (set.add(key)) {             // 第一次见到该 key
                    return downstream.push(element);
                }
                return !downstream.isRejecting();   // 继续处理
            }
    );
}

var result = Stream.of("foo", "bar", "baz", "quux")
        .gather(distinctBy(String::length))
        .toList();
// [foo, quux]

Gatherer 的四个组成部分:

组件 说明
initializer 创建私有状态对象
integrator 处理每个元素,可向下游推送 0 个、1 个或多个元素
combiner 合并并行执行的中间状态
finisher 输入结束后做最终处理

能力范围:

  • 一对一 :如 map
  • 一对多 :如 flatMap
  • 多对一 :如 reduce
  • 多对多:如窗口分组
  • 短路:可提前结束(把无限流转为有限流)
  • 有状态:可跟踪已见元素
  • 并行:可实现并行友好的 Gatherer

演进路径:

版本 JEP 状态
22 461 预览
23 473 第二次预览
24 485 正式

7. JEP 462:结构化并发(第二次预览)

相比 JDK 21(JEP 453)的主要改进是 API 微调与 StructuredTaskScope 的完善。

java 复制代码
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;

Response handle() throws Exception {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Subtask<String> user  = scope.fork(() -> findUser());
        Subtask<Integer> order = scope.fork(() -> fetchOrder());

        scope.join().throwIfFailed();

        return new Response(user.get(), order.get());
    }
}

8. JEP 463:隐式声明类与实例 main 方法(第二次预览)

(JDK 21 的 JEP 445 首次预览,此处为第二次。)

目标:让 Java 的「Hello World」更简单,降低入门门槛。

java 复制代码
// JDK 21/22 预览:隐式声明类
void main() {
    System.out.println("Hello, World!");
}

关键变化:

  • 顶层方法(不在 class 中)------隐式声明类
  • main 方法可以是实例方法 (不需要 static)
  • main 方法可以没有参数 (不需要 String[])
  • 隐式类自动 import 常用 API(java.util 等)

三层渐进:

java 复制代码
// ① 最简:隐式类 + 实例 main
void main() {
    System.out.println("Hello");
}

// ② 显式类名,仍是实例 main
class HelloWorld {
    void main() {
        System.out.println("Hello");
    }
}

// ③ 传统形式(仍然完全支持)
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}

main 方法的选择规则:

  1. 优先选择带 String[] 参数的
  2. 其次选择无参数的
  3. 可以是 static 或实例方法
  4. 可以 public、protected 或包私有(不能 private)

隐式类的限制:

  • 只能放在未命名包中
  • 自动 implements 无接口
  • 自动 extends Object
  • 不能被其它类引用(无名)
  • 编译产物的类名由文件名推导(Main.java → Main)

自动导入的包(隐式类中):

  • java.lang
  • java.io
  • java.math
  • java.net
  • java.nio.file
  • java.util
  • java.util.concurrent
  • java.util.function
  • java.util.stream

(JDK 25 的 JEP 512 转正,改名为 Compact Source Files and Instance Main Methods。)

9. JEP 464:Scoped Values(第二次预览)

(JDK 20 孵化、JDK 21 预览,此处第二次预览。)

java 复制代码
import java.lang.ScopedValue;

public class Server {
    static final ScopedValue<Request> REQUEST = ScopedValue.newInstance();

    void handle(Request request) {
        ScopedValue.where(REQUEST, request).run(() -> {
            // 此处及以下调用链中都可访问 REQUEST.get()
            process();
        });
    }

    void process() {
        Request req = REQUEST.get();
        // ...
    }
}

(JDK 25 的 JEP 506 转正。)

10. JEP 459:String Templates(第二次预览)

(JDK 21 JEP 430 首次预览。)注意:该特性在 JDK 23 中被搁置,未继续推进。

java 复制代码
String name = "Alice";
String s = STR."Hello, \{name}!";

11. JEP 460:Vector API(第七次孵化)

继续孵化,改进对 ARM 与 x86 的支持。

java 复制代码
import jdk.incubator.vector.*;

static final VectorSpecies<Integer> SPECIES = IntVector.SPECIES_PREFERRED;

int[] addArrays(int[] a, int[] b) {
    int[] result = new int[a.length];
    int i = 0, bound = SPECIES.loopBound(a.length);
    for (; i < bound; i += SPECIES.length()) {
        IntVector va = IntVector.fromArray(SPECIES, a, i);
        IntVector vb = IntVector.fromArray(SPECIES, b, i);
        va.add(vb).intoArray(result, i);
    }
    for (; i < a.length; i++) result[i] = a[i] + b[i];
    return result;
}

12. JEP 464 / 其它

(已在上述章节覆盖。)


三、JDK 22 特性速查表

类别 JEP 特性 状态
库 454 外部函数与内存 API 正式
语言 456 Unnamed Variables & Patterns 正式
工具 458 启动多文件源码程序 正式
GC 423 G1 的 Region Pinning 正式
库 461 Stream Gatherers 预览
库 457 Class-File API 预览
库 459 String Templates 第二次预览(搁置)
库 460 Vector API 第七次孵化
并发 462 Structured Concurrency 第二次预览
并发 464 Scoped Values 第二次预览
语言 463 隐式声明类与实例 main 第二次预览

四、升级注意事项

  1. FFM API 转正(JEP 454) :可放心在生产使用;但注意 JDK 24 起会要求 --enable-native-access
  2. _ 成为关键字(JEP 456) :检查代码中是否有名为 _ 的标识符
  3. 多文件源码启动:适合脚本与原型,不适合大型项目(每次重新编译)
  4. String Templates 已被搁置:不要在生产代码中使用
  5. Stream Gatherers 是预览:JDK 24 转正
  6. Class-File API 是预览:JDK 24 转正
  7. 结构化并发 / Scoped Values 仍在预览:API 还会变化

五、参考资源


← JDK 21 | 返回索引 | JDK 23 →

相关推荐
余槐i1 小时前
开虚拟线程后接口反而更慢:JDK 21 synchronized 锁住载体线程的排查
java·spring boot·redis·mysql·性能优化
“AI国潮设计-小江”2 小时前
AIGC实战:Python+SDXL批量生成英歌舞IP立牌,解锁猪猪蛋糕“反差萌”流量密码(附完整API源码)
开发语言·人工智能·python·prompt·aigc
Sirens.2 小时前
网络通信基础:IP、端口、协议分层与数据封装
java·网络·网络协议·tcp/ip·java-ee·1024程序员节
H.莓飛2 小时前
【数据结构】排序_OJ题(二):划分与选择
linux·服务器·c语言·开发语言·数据结构·visual studio
CRMEB系统商城2 小时前
服装连锁的导购分销与企微私域
java·大数据·开发语言·人工智能·小程序·php·1024程序员节
若亦_2 小时前
Java泛型入门:类型安全与上界约束
java·1024程序员节
凉茶钱2 小时前
stack、queue、priority_queue、容器适配器完整梳理
开发语言·c++·queue·1024程序员节·stack
编程的一拳超人2 小时前
【元宝搜索开发者征文大赛】四语实战:Java/Python/Go/Node.js 构建 AI 搜索应用全攻略
java·python·golang·node.js·agent
wuminyu2 小时前
JVM规范学习总结-Java源码经过javac编译后产物
java·linux·c语言·jvm·c++