mcpField
J
J2C Java to C++

J2C Java to C++

本地服务
github.com开发工具03 天前更新

关于 J2C Java to C++

J2C 是 Java 源码到 C++20 的转换器,包含命令行工具和 VS Code 扩展。它适合评估或推进 Java 项目的迁移,并提供检查、修复及运行对比;功能支持仍在逐步完善,生成代码可能需要手动调整。

主要功能

  1. 01将 Java 源码转换为 C++
  2. 02检查不支持的语法并生成迁移报告
  3. 03应用部分问题的机械修复
  4. 04构建并运行 C++,与 JVM 输出对比
  5. 05将编译诊断映射回 Java 行号

适用场景

  1. 01迁移前评估项目的可转换范围
  2. 02定位 Java 代码中的转换阻碍
  3. 03修复已支持自动处理的问题
  4. 04验证 C++ 程序与 JVM 程序的运行结果

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"juangp3-j2c": {
"command": "npx",
"args": [
"-y",
"@juangp3/j2c-mcp"
]
}
}
}
启动命令npx -y @juangp3/j2c-mcp

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npm@juangp3/j2c-mcp0.6.1

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

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.lineDirectivesfalse添加 #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 中。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。