关于 J2C Java to C++
J2C 是 Java 源码到 C++20 的转换器,包含命令行工具和 VS Code 扩展。它适合评估或推进 Java 项目的迁移,并提供检查、修复及运行对比;功能支持仍在逐步完善,生成代码可能需要手动调整。
主要功能
- 01将 Java 源码转换为 C++
- 02检查不支持的语法并生成迁移报告
- 03应用部分问题的机械修复
- 04构建并运行 C++,与 JVM 输出对比
- 05将编译诊断映射回 Java 行号
适用场景
- 01迁移前评估项目的可转换范围
- 02定位 Java 代码中的转换阻碍
- 03修复已支持自动处理的问题
- 04验证 C++ 程序与 JVM 程序的运行结果
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
J2C Modern Edition
J2C 可将 Java 源代码转换为 C++。本仓库打包了独立的命令行转换器和 J2C Java to C++ VS Code 扩展。该项目是 arnetheduck/j2c 的现代化延续;其原始 Eclipse 插件仍保留在该仓库中。
预览版: J2C 是源代码转换器,并非完整的 Java 编译器或运行时。对 Java 功能的支持是逐步增加的。生成的 C++ 可能需要手动修改,并需要兼容的 Java 运行时行为实现。不要假定任意 Java 程序都能编译,或能保留所有 JVM 语义。
要求
- 运行 CLI 和 VS Code 扩展需要 Java 11 或更高版本。
- 从源代码构建 CLI 需要 Maven。
- 生成的 Makefile 和 C++ 输出需要 C++20 编译器。
- 打包 VS Code 扩展需要 Node.js 22 或更高版本以及 npm。
CLI
在仓库根目录构建可执行 JAR:
mvn package
创建输出目录,并翻译 Java 文件或源代码目录:
mkdir generated
java -jar target/j2c-cli.jar \
--output generated \
--sourcepath test/src \
test/src/se/arnetheduck/j2c/test/Hello.java
多个源代码根目录或类路径条目之间使用平台路径分隔符(Windows 上为 ;,macOS/Linux 上为 :)。源代码路径有助于解析 Java 源代码依赖项;类路径条目应指向依赖 JAR 或类目录。如果省略 --sourcepath,J2C 会根据所选输入推断根目录。
--sealed-types 策略默认为 advisory。密封声明会使用普通 C++ 继承进行翻译,生成的注释会列出允许的直接子类型;C++ 不会强制执行 Java 的 permits 限制。选择 --sealed-types reject,可在遇到密封或非密封声明时停止翻译。
其他可选参数包括 --classpath <paths>、--name <name>、--sealed-types advisory|reject 和 --encoding <charset>。除非 --encoding 另有指定,否则 Java 源代码会以 UTF-8 读取;生成的 C++ 始终以 UTF-8 写入。
使用 J2C 运行时进行翻译,这样无需编写任何 C++ 即可运行程序:
java -jar target/j2c-cli.jar --runtime bundled --output generated test/runtime/hello
make -C generated all mains && generated/bin/Hello
--runtime bundled 使用 CLI JAR(以及 VS Code 扩展)中打包的运行时,因此无需检出仓库:首次使用时会将其提取到 ~/.j2c/cache/runtime-<version>-<hash>(或 J2C_CACHE_DIR),只要其中的文件完好无损,就会重复使用;否则会重新提取。--runtime <directory> 使用其他运行时目录,例如检出仓库中的 runtime/(若目录名为 bundled,请写作 ./bundled)。不指定 --runtime 时,默认情况下,java.* 来自正在运行的 JDK,且每个 JDK 类都是一个在运行时会失败的存根;指定该参数后,java.* 来自运行时,因此运行时未提供的 JDK 类会导致编译错误,并由 --check 列出。有关运行时涵盖的内容,请参阅 runtime/README.md。
j2c run 可一步完成程序翻译、构建和运行,在 JVM 上运行相同的主类,并比较两者:
java -jar target/j2c-cli.jar run --output generated test/runtime/hello -- one "two words"
java -jar target/j2c-cli.jar run --output generated --format json --main Stdin --stdin test/runtime/stdin/input.txt test/runtime/stdin test/runtime/hello
它使用运行时(--runtime,默认为 bundled)进行翻译,并启用 --line-directives;构建主类的二进制文件(make bin/<Class>;运行 j2c build 时会调用 make,因此构建失败时会报告对应 Java 行上的编译器错误);在全新的工作目录中运行该二进制文件,使用 -- 之后的参数以及 --stdin <file> 的字节作为标准输入;使用运行 J2C 的 JDK 中的 javac 编译相同的源代码,并通过 java 运行该类(如果构建在 WSL 中运行,则使用 --java <program>,否则依次使用 J2C_WSL_JAVA、WSL PATH 中的 java,或运行 J2C 的 Windows JDK,并附带说明)。它会比较标准输出、去除 JVM 堆栈帧后的标准错误以及退出代码,并打印 C++ 程序的输出、每次运行的结束方式以及首个差异行。
--main <class> 可在多个带有 main 方法的类中进行选择;--no-compare 只运行 C++ 程序;--timeout <seconds>(默认为 60,0 表示不限制)会停止每次运行及其启动的所有进程;--output-limit <characters>(默认为 20000)会限制每个流报告的输出长度。使用 --format json 时,结果包含 status(ok、differs、failed、crashed、timeout 或 build-failed)、cpp 和 jvm(退出代码、signal、timedOut、durationMillis、stdout 和 stderr,以及各自的字节数和是否被截断)、comparison(equal、differs 或 skipped,并包含各个流的 firstDifference:line、cpp 和 jvm)以及 build。
在写入任何内容之前,检查代码是否可以翻译:
java -jar target/j2c-cli.jar --check --sourcepath src/main/java src/main/java
--check 接受与翻译相同的输入,但不需要 --output。它会以 file:line: message 的形式打印所选文件中的所有编译错误和不支持的构造,随后按类型汇总,且不会写入任何文件。选择一个源代码根目录即可检查整个项目。--version 会打印 CLI 版本。
Maven 和 Gradle 项目
传入一个项目目录(包含 pom.xml、build.gradle(.kts) 或
settings.gradle(.kts) 的目录),J2C 就会根据构建配置完成设置:
java -jar target/j2c-cli.jar --check .
java -jar target/j2c-cli.jar --tests --output generated .
- 源代码根目录: Maven 会使用
<modules>中列出的每个模块的src/main/java(或<sourceDirectory>),以及build-helper-maven-plugin添加的根目录;Gradle 会使用根项目和每个include的子项目中的src/main/java,或 Gradle 报告的源集。使用--tests时,也会包含测试根目录(src/test/java、<testSourceDirectory>)。 - 类路径: J2C 会运行项目的包装器(
mvnw、gradlew),否则会从PATH中运行mvn/gradle。Maven 会运行dependency:build-classpath(编译范围;使用--tests时为测试范围);在模块之间存在依赖的多模块构建中,还会运行compile阶段并跳过编译,以便运行绑定到更早阶段的插件。Gradle 会收到一个初始化脚本(--init-script),用于输出compileClasspath和testCompileClasspath;如果失败,则会使用--offline重试。构建过程可能会下载依赖项。其他模块的输出会被排除,因为这些模块的源代码也会被翻译。 - 检测到的内容和运行的命令会输出到 stderr,并作为
projects出现在 JSON 输出中。解析出的类路径会缓存到~/.j2c/cache(或J2C_CACHE_DIR),并以构建文件的哈希值作为键。 - 如果缺少构建工具或运行失败,J2C 仍会使用源代码根目录,并报告警告
J2C-PROJECT-CLASSPATH-UNRESOLVED(在 JSON 的warnings中);此时依赖类型将无法解析。
--no-build-tool 绝不会运行 Maven 或 Gradle(但仍会使用缓存的类路径)。--project none 会关闭检测;--project <directory> 则会使用该目录的构建配置处理其他输入,例如目录中的单个文件。检测只会检查输入目录本身,不会检查其父目录。
输出目录必须为空,或是先前的 J2C 输出(此类输出会通过 .j2c-output 文件标记)。开始翻译时,会先将目录当前内容移至临时文件夹中的备份,因此除非传入 --force,否则 J2C 会拒绝处理其他非空目录。在此之前,会检查每个选定文件:如果有文件存在问题,翻译就会失败并报告所有问题,同时保持目录原样。构建生成的对象文件和库(obj/ 和 bin/)会保留;如果生成文件与先前内容相同,则会保留原有修改时间。因此,make(以及 j2c build、j2c run 和 j2c test)只会重新编译发生变化的内容:首次构建后,针对运行时运行时不再需要全部重新编译。更改编译器或其标志(CXX、CFLAGS)后,所有内容都会重新编译。
仅作为依赖项的文件(在源代码路径中找到,但未被选中)若无法编译,会像库类一样被翻译为头文件和存根,不包含实现;同时会报告警告 J2C-DEPENDENCY-ERRORS(在 JSON 的 warnings 中),而不是报错。
每个问题都有一个稳定的 ID(例如 J2C-LAMBDA-ANONYMOUS-THIS)。j2c explain <id> 会说明问题及其修复方法;j2c explain 会列出所有问题。--format json 会让翻译和 --check 输出单个 JSON 对象(包含 ID、文件、行、列、消息和提示的问题;或生成的文件)。j2c capabilities 会以 JSON 格式输出支持的功能和限制。
部分问题可以通过机械方式修复;j2c fix 会在 Java 源代码中进行相应修改:
java -jar target/j2c-cli.jar fix src/main/java # preview as a unified diff
java -jar target/j2c-cli.jar fix --apply src/main/java # write the fixes
它接受与 --check 相同的输入和选项(但不包括 --output、--name
和 --force)。预览会在 stdout 上打印可由 git apply 接受的差异;
--apply 会写入修复、再次检查,并重复此过程,直到没有可修复的问题
为止。以下问题有对应的修复:J2C-LAMBDA-ANONYMOUS-THIS(在 lambda
中使用的 var self = this; 局部变量)、J2C-LAMBDA-INTERFACE-SUPER
(将 Iface.super.m() 调用移入私有方法)、
J2C-PATTERN-OUTSIDE-METHOD(将字段初始化器移入私有方法)、
J2C-TEST-TIMEOUT(移除超时)和 J2C-TEST-PRIVATE-METHOD(移除
private)。使用 --format json 时,每个问题都会列出其修复,形式为精确的编辑项(file、包含从 1 开始计数的
line 和 column 的 range,以及 newText)。
j2c report 会在开始迁移之前评估迁移情况:
java -jar target/j2c-cli.jar report . > migration-report.md # Markdown
java -jar target/j2c-cli.jar report --format json src/main/java # JSON
它接受与 --check(以及 --runtime)相同的输入和选项,并显示可原样转换的文件、类型和方法(它们内部没有来自 --check 的问题)、按问题 ID 和软件包划分的阻碍项及其主要位置和 j2c fix 可修复的数量、使用 --tests 时的测试功能阻碍项、代码使用的 JDK 类型和成员与 J2C 运行时提供的内容之间的对比(其余部分会是运行时会失败的存根),以及后续步骤。
--line-directives 会向生成的 C++ 添加 #line 指令,使编译器错误和调试器(gdb:break Foo.java:21、list)指向 Java 文件和行。每个 C++ 行都映射到它所来源的 Java 语句,lambda 中的语句也不例外;生成的样板代码仍使用其 C++ 文件和行。指令中的路径相对于输出目录,也就是运行 make 的目录。不使用此选项时,生成的文件不会改变。
j2c build 会编译转换后的代码,并将编译器错误报告在对应的 Java 行上:
java -jar target/j2c-cli.jar build --output build/cpp src/main/java # translate with directives, then make
java -jar target/j2c-cli.jar build --output build/cpp --format json # build earlier J2C output
它会在输出目录中运行 make -k(在 Windows 上,如果 make 不在 PATH 中,则使用 WSL;可使用 --wsl auto|always|never、--wsl-distro <name>、
--make <program>、--target <target>、--jobs <n>,或变量
J2C_MAKE、J2C_WSL 和 J2C_WSL_DISTRO),解析 GCC 和 Clang
诊断信息,并将每条信息连同其 C++ 位置一起打印在对应的 Java 文件和行上。生成代码中不属于任何 Java 语句的诊断信息会以 C++ 位置报告,并附上其之前最近的 Java 行。使用 --format json 时,结果包含 status、exitCode、diagnostics(severity、message、java、cpp、mapped、notes、includedFrom)、summary 和 log 的末尾内容。
CLI 成功时(或检查无问题时)以状态码 0 退出,转换失败或检查发现问题时以状态码 1 退出,参数无效时以状态码 2 退出。j2c build 在 make 成功时以 0 退出,否则以 1 退出;j2c test 在没有测试失败时以 0 退出,否则以 1 退出;j2c run 在程序行为与 JVM 上一致时(或使用 --no-compare 时以 0 退出)以 0 退出,否则以 1 退出。j2c fix 在没有需要手动修改的遗留问题时(应用修复后,或预览无法修复时)以 0 退出,否则以 1 退出。j2c report 只要打印了报告,就以 0 退出。
单元测试(JUnit 转 GoogleTest)
--tests 会转换 JUnit 4 和 JUnit 5 测试类,并为每个可运行的测试类生成一个 GoogleTest 文件,以便项目自身的测试能够针对 C++ 转换结果运行:
java -jar target/j2c-cli.jar --tests --output generated \
--sourcepath src/main/java:src/test/java \
--classpath junit-jupiter-api.jar src/main/java src/test/java
make -C generated test
GoogleTest 只负责运行测试并报告结果。每个测试都会创建一个新实例,与 JUnit 中一样;@BeforeAll、@BeforeEach、@AfterEach 和 @AfterAll(以及它们在 JUnit 4 中的对应项,包括继承的项)会按 JUnit 的顺序运行。@Disabled 和 @Ignore 测试会跳过,并显示其原因;@Test(expected = ...) 会检查抛出的异常。与 JUnit 一样,测试在抛出 Java 异常时失败。
JUnit 断言和前置条件会转换为遵循 JUnit 规则的 C++ 辅助函数,因此无需 Java 运行时即可使用:
assertTrue和assertFalse;assertNull和assertNotNull。assertEquals和assertNotEquals,包括浮点数误差范围形式。双精度浮点数的比较方式与 JUnit 相同:NaN 等于 NaN,0.0 与 -0.0 不同。assertSame和assertNotSame;assertArrayEquals;fail。- 使用类字面量的
assertThrows,它会返回异常;以及assertDoesNotThrow。 assumeTrue和assumeFalse,用于跳过测试。
这些断言既适用于 JUnit 5 的 Assertions,也适用于 JUnit 4 的 Assert。断言失败时会抛出 Java AssertionError,并附上相应 JUnit 版本给出的消息,例如 expected: <4> but was: <5>(JUnit 5)或 expected:<4> but was:<5>(JUnit 4)。对象相等性比较会调用翻译后的 equals(),因此适用于项目自身的类。
使用 J2C 运行时(--tests --runtime bundled)翻译后,断言还会通过 Java 的 equals 比较 JDK 对象(字符串、装箱数字、列表、映射),并像 JUnit 一样显示值和消息:
- 值会使用其 Java
toString输出,因此会显示为expected: <[1, 2]> but was: <[1, 3]>和expected: <0.3> but was: <0.30000000000000004>; - JUnit 5 的自定义消息会放在前面,例如
names ==> expected: <a> but was: <b>;只有断言失败时才会调用Supplier<String>消息; - JUnit 4 会在两个字符串的差异处将其压缩显示,例如
expected:<ab[c]def> but was:<ab[X]def>,并在消息前加上前缀,例如name expected:<[a]> but was:<[b]>; AssertionError会携带消息,因此捕获该异常的 Java 代码可以获取消息,GoogleTest 也会像显示其他异常消息一样显示它。
不使用运行时时,JDK 对象都是存根:消息会按对象类型描述对象,也不会显示自定义消息。其他形式(assertAll、assertTimeout、惰性条件、Hamcrest、AssertJ、Truth)会由 --check --tests 报告。
j2c test 会一步完成所有这些操作,并在 Java 方法处报告每项测试:
java -jar target/j2c-cli.jar test --output generated --runtime bundled \
--sourcepath src/main/java:src/test/java src/main/java src/test/java
java -jar target/j2c-cli.jar test --output generated --format json \
--filter CounterTest,ParserTest.parsesEmpty src/main/java src/test/java
它会使用 --tests 和 --line-directives 进行翻译,构建 GoogleTest 二进制文件(make tests,运行方式与 j2c build 执行 make 相同,因此构建失败时会在对应 Java 行报告编译器错误),运行二进制文件,并逐行打印每项未通过测试的信息,包括其 Java 文件和行号、JUnit 消息,最后显示计数。使用 --format json 时,每项测试会包含 javaClass、javaMethod、file、line、status(passed、failed、skipped、disabled,或测试进程先崩溃或超时时的 notRun)、message、exception、timeMillis 和 cppTest;summary 则包含计数和测试二进制文件的退出代码。--filter 接受测试类、Class.method、Class#method 或方法名(以逗号分隔);--timeout <seconds>(默认 300,0 表示不限制)会停止测试进程及其启动的所有进程。
make test 需要 GoogleTest(通过 pkg-config 查找,或设置 GTEST_CFLAGS 和 GTEST_LIBS);与任何已翻译的程序一样,还需要测试所用 JDK 类的 C++ 实现:J2C 运行时,或通过 TEST_LDLIBS 传入的自有实现。
仓库自带的 JUnit 测试夹具(test/junit,以及用于 JDK 对象的 test/junit-runtime)会以三种方式运行;所有结果和记录的失败消息都必须与 expected-results.json 一致:在 JVM 上使用真实 JUnit 运行(npm run junit:jvm,会通过 Maven 将 JUnit 下载到 target/),以及翻译后使用 GoogleTest 运行,分别针对存根(npm run junit:gtest)和运行时(npm run junit:gtest:runtime)。
使用 --tests 时,--check 还会报告封装器无法运行的测试功能:模拟库(Mockito、PowerMock、EasyMock)、运行器、规则和扩展、参数化测试、重复测试和动态测试、@Nested 类、超时、@TestInstance(PER_CLASS),以及私有或带参数的测试方法。每项都会提供问题 ID 和建议的修复方法。
面向新 C++ 测试的 gMock 类
--mocks 会为已翻译文件中的每个顶层接口和成员接口生成一个 gMock
类,以便手写的 C++ 测试可以向已翻译代码传入 mock:
mock/<package>/Mock<Interface>.hpp 声明了 <package>::Mock<Interface>,
该类继承已翻译的接口。
java -jar target/j2c-cli.jar --mocks --output generated src/main/java
cp repository_test.cpp generated/test/
make -C generated tests USER_TEST_SRCS=test/repository_test.cpp
#include <gmock/gmock.h>
#include <demo/MockRepository.hpp>
#include <demo/Service.hpp>
TEST(Service, savesOnce)
{
demo::MockRepository repository;
EXPECT_CALL(repository, save(::testing::_)).Times(1);
demo::Service(&repository).run();
}
- 每个抽象方法都会使用
MOCK_METHOD进行 mock,包括从父接口继承的方法,并使用转换器采用的 C++ 类型: 泛型类型和泛型方法会被擦除(Repository<T>中的T get(int)会变为MOCK_METHOD(::java::lang::Object*, get, (int32_t), (override))),数组和可变参数会转换为 J2C 数组,重载方法会分别 mock(参数数量相同时,可使用::testing::A<int32_t>()等类型匹配器来匹配)。 - 默认方法不会被 mock:它们保留 Java 方法体,并调用已 mock 的方法。静态方法和私有方法不是虚方法;接口重新声明的
java.lang.Object方法(equals、hashCode、toString)会保留 Object 的实现,与已翻译的类一致。 - 使用另一种擦除形式重写继承方法的函数(例如,
Sink<Item>.accept(T)对应的void accept(Item))会获得已翻译 Java 类应有的桥接方法,因此通过任一 C++ 函数签名调用都会到达同一个 mock。 make tests会将USER_TEST_SRCS中(位于test/)的文件编译到 GoogleTest 二进制文件中,使用-Imock,并链接 gMock(pkg-config gmock,或设置GMOCK_CFLAGS和GMOCK_LIBS)及 GoogleTest。未使用--runtime时,请使用TEST_LDLIBS链接运行时钩子的 C++ 实现,与--tests的做法相同。
gMock 无法表达的内容会导致翻译停止(--check --mocks 会列出这些内容),并标记为 J2C-MOCK-UNSUPPORTED:参数超过 15 个的方法(MOCK_METHOD 最多支持 15 个参数);以及继承后返回类型被擦除、但没有任何单一 C++ 重写能够接受的方法,例如 Java 中返回 Integer 的 value() 继承后返回类型为 Number 和 Comparable(请在接口中声明该方法)。在 vscode/ 中运行 npm run mocks:gtest,会构建并运行仓库自带的示例 test/cpp/Java17MocksTest.cpp,并编译从 test/src 生成的所有 mock。
将 J2C 与 AI Agent 配合使用
j2c mcp 会将 J2C 作为模型上下文协议服务器提供服务,因此 GitHub Copilot agent mode、Claude Code 和 Claude Desktop 等 Agent 可以通过类型化工具检查、修复、翻译和解释问题。VS Code 扩展会为每个工作区文件夹自动注册该服务器。对于 Claude Code:
claude mcp add j2c -- java -jar /path/to/j2c-cli.jar mcp --root .
这些工具包括 j2c_check、j2c_fix、j2c_report、j2c_translate、
j2c_build、j2c_test、j2c_run、j2c_explain 和 j2c_capabilities;服务器还提供提示词
j2c-migrate 和 j2c-assess,以及问题目录、运行时契约和已翻译文件等资源。
服务器会在多次调用之间保留检查结果,因此编辑后再次检查时,只会编译变更的文件及使用这些文件的文件,结果与全新检查一致(--check-cache <files> 用于限制保留的内容;
0 会关闭缓存)。对于请求进度的客户端,工具调用会报告进度;调用可以取消,并且在工具运行期间也会响应 ping。
请参阅 docs/AGENTS.md,了解各客户端的设置方法、工具参考和推荐工作流。项目还包含现成的 Claude Code 技能、包含该技能和服务器的 Claude Code 插件、Copilot 指令以及 Copilot 云端编码 Agent 的配置。
VS Code 扩展
该扩展在 Java 编辑器和资源管理器上下文菜单中提供 J2C: Translate to C++。选择一个 Java 文件或源文件夹,然后选择一个现有的输出目录。问题会显示在“问题”面板中;对于可以机械修复的问题,会提供快速修复。J2C: Build Translated C++ 会在输出目录中运行 make,并在“问题”面板中显示编译器错误;启用 j2c.lineDirectives 时,错误会定位到对应的 Java 行。
Marketplace 版本尚未发布。构建本地打包的 VSIX 后,可按以下步骤安装:
code --install-extension <packaged-extension.vsix>
该扩展会调用随附的 CLI,因此需要 Java 11 或更高版本。 要在本地准备扩展及其随附的 CLI:
cd vscode
npm run vscode:prepublish
此操作会在扩展目录中构建并暂存 CLI JAR。安装 VSIX 之前,请使用 VS Code Marketplace 打包工具(vsce)打包扩展。
配置:
| 设置 | 默认值 | 用途 |
|---|---|---|
j2c.sourcePaths | [] | Java 源代码根目录;默认使用 java.project.sourcePaths,然后使用惯例的 src 文件夹。 |
j2c.classPath | [] | 依赖 JAR 或类目录。 |
j2c.cliJar | "" | 可选的自定义 CLI JAR 路径;否则使用打包的 CLI。 |
j2c.javaExecutable | "java" | 用于运行 CLI 的 Java 可执行文件。 |
j2c.sealedTypes | "advisory" | advisory 会生成普通继承并允许使用注释;reject 会拒绝 sealed 和 non-sealed 声明。 |
j2c.runtime | "bundled" | bundled 会针对扩展中附带的 J2C 运行时进行转换,因此程序可以运行(不支持的 JDK 类会导致编译错误);none 会使用存根针对 JDK 进行转换,这是 CLI 的默认设置;也可以指定运行时目录。 |
j2c.lineDirectives | false | 添加 #line 指令,使编译器错误和调试器引用 Java 行。 |
j2c.build.make、j2c.build.wsl、j2c.build.wslDistribution | ""、"auto"、"" | J2C: Build Translated C++ 运行 make 的方式(在 Windows 上,如果缺少 make,则在 WSL 中运行)。 |
j2c.project | "auto" | auto 会在工作区文件夹中使用 Maven 或 Gradle 构建来获取源代码根目录和依赖类路径(参见 Maven 和 Gradle 项目);none 会关闭此功能。 |
Java 功能支持
已启用 Java 17 解析。已翻译的语言功能:
- 模式匹配
instanceof可出现在任何允许条件或表达式出现的位置(&&、?:、循环、取反的if),并遵循 Java 的流作用域:在if (!(o instanceof T t)) return;之后,仍可使用t。 - Switch 表达式和语句支持箭头形式(
case X ->)和冒号形式(case X:),选择器支持基本类型、装箱类型、枚举和字符串。 - 文本块。
- Lambda 表达式和方法引用(静态、绑定、未绑定、
this::、super::、构造函数、数组构造函数和泛型函数方法),包括交叉类型强制转换目标,例如(Runnable & Serializable) () -> ...。每个表达式都会被改写为 Java 定义的等效匿名类,并像其他匿名类一样翻译,因此每个 Lambda 都会变成一个小型 C++ 类。绑定接收者(expr::method)在创建引用时求值一次;接收者为 null 时,不会在创建引用时失败,而是在调用引用时失败。 - 接口的默认方法、静态方法和私有方法。
- Try-with-resources,包括
try (existingResource)。资源会按相反顺序关闭,然后才运行catch和finally子句。如果已有异常正在传播时close()又抛出异常,程序会终止,而不会将其记录为被抑制的异常。 - Record,包括生成的组件存储和访问器、构造、
equals、hashCode和toString。 - 按上述建议/拒绝策略处理 sealed 和 non-sealed 声明。
整数运算遵循 Java 而非 C++ 的规则:除数或余数为零时抛出 ArithmeticException,MIN_VALUE / -1 会回绕,移位计数会按类型宽度屏蔽,浮点数转整数的强制转换会饱和(NaN 变为 0)。生成的 Makefile 会使用 -fwrapv 编译,使溢出回绕;如果以其他方式构建输出,请保留此标志。
数组访问会像 Java 中一样进行检查:索引越界时抛出 ArrayIndexOutOfBoundsException(Index 5 out of bounds for length 3),大小为负数时抛出 NegativeArraySizeException。如需舍弃索引检查以提高速度,可使用 CFLAGS=-DJ2C_NO_BOUNDS_CHECKS make 构建;此时错误索引会导致未定义行为,与 C++ 一样。
请检查每个项目生成的 C++。特别是,Record 组件赋值、数组身份哈希和引用字符串化可能与 JVM 行为不同。**生成的程序无法独立运行:**JDK 类(String、集合、System.out 等)会生成为调用 unimplemented_() 的存根,因此必须为所用 Java 类提供运行时实现。--runtime bundled 使用 J2C 自带的运行时,该运行时打包在 CLI 中(runtime/README.md):包含 java.lang 的核心部分、集合、流、String.format、正则表达式、文本输入和文件。
默认情况下,生成的对象永远不会被释放。make J2C_GC=boehm 会使用 Boehm 垃圾回收器链接程序(Debian 和 Ubuntu 上的 libgc-dev,通过 pkg-config bdw-gc 查找,或设置 GC_CFLAGS 和 GC_LIBS),此后所有分配都会交由该回收器处理,因此内存会像在 Java 中一样回收。启用或停用它时,请运行 make clean。它需要 GNU ld(--wrap),适用于 make mains,但不适用于 make test,且终结器不会运行。运行时 Thread.start 创建的线程会向回收器注册。
以下语法会被检测到,并停止翻译,同时给出 file:line 诊断,而不是生成错误的 C++:
- 在匿名类中使用
this的 Lambda 和方法引用(生成的类无法命名该匿名类),或交叉类型强制转换目标使用类型变量的 Lambda 和方法引用。 - 方法体以外的
instanceof模式,例如字段初始化器中的模式。
使用 --check 一次列出所有这些问题。
构建和验证
构建 CLI:
mvn package
准备扩展:
cd vscode
npm run vscode:prepublish
test/src 目录包含翻译器测试样例。并非所有 Java 特性或编译器/工具链组合的生成结果都经过验证。在 vscode 目录中运行 npm run vscode:prepublish 会执行许可证检查,以及 CLI 和扩展集成测试套件。CI 还会翻译 Java 17 测试样例、编译每个生成的项目,并在 UndefinedBehaviorSanitizer 下对控制流、记录、lambda、instanceof 模式和整数运算运行原生冒烟测试。这些检查不能保证语义一致或普遍兼容 Java。
test/probes 中存放特性探测文件,每个文件的第一行都会声明它应当编译还是被拒绝。在 vscode 目录中运行 npm run probe 会翻译所有探测文件,在 make 可用时编译它们,并在结果发生变化时失败,从而发现新增的缺口和已修复的问题。对于你想检查的任何 Java 结构,都可以添加探测文件。
预览候选版本的范围和变更请参阅 CHANGELOG.md。
项目关系和许可
J2C Modern Edition 是从 arnetheduck/j2c 派生的独立项目。原项目的历史和 Eclipse 插件仍由上游维护。本项目采用 Eclipse Public License 1.0 许可,详见 LICENSE。扩展打包步骤会将该许可证复制到 VSIX 中。第三方依赖各自采用不同的许可证;详见 THIRD-PARTY-NOTICES.txt 和 licenses/,其中包含完整的许可证文本、每个依赖项的原始声明文件,以及获取其源代码的方式。两者都包含在 CLI JAR(位于 META-INF/j2c-licenses/ 下)和 VSIX 中。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。