Java 单元测试自动化:手把手教你用 Claude Skills 生成高质量测试代码

Java 单元测试自动化:手把手教你用 Claude Skills 生成高质量测试代码

前言

在日常的 Java 开发中,你是否也遇到过这样的困境:

  • 写测试代码比写业务代码还累?
  • 每次都要重复编写类似的 Mock 配置?
  • 团队成员的测试风格五花八门,难以维护?
  • 测试覆盖率不达标,但又不知道从何下手?

作为一名 Java 开发者,我们深知单元测试的重要性,但真正落地时却往往因为各种原因而草草了事。今天,我要介绍一个彻底改变这一现状的利器——Claude Skills

通过将测试规范封装成 Skill,我们可以让 Claude 严格按照团队标准自动生成高质量、可维护的单元测试代码。不仅大幅提升开发效率,还能保证测试代码的一致性和可读性。

一、什么是 Claude Skills?

1.1 核心概念

Claude Skills 本质上是一个结构化的操作手册。它将你的经验、流程、规范打包成一个可复用的技能包。当需要执行某项任务时,Claude 会自动加载对应的 Skill,按照预先定义的规则工作。

一个 Skill 的文件结构非常简单:

.claude/skills/ └── java-unit-test/ └── SKILL.md # 核心指令文件 

1.2 为什么需要 Skill?

传统方式使用 Skills
每次对话都要重复描述测试规范一次定义,永久复用
测试风格因人而异,难以维护团队统一标准
提示词容易遗漏关键细节完整 SOP 固化在 Skill 中
难以迭代优化集中管理,随时更新

核心理念:Skills > Agents —— 相比一次性的对话,持续积累的 Skills 才是真正的生产力资产。

二、创建 Java 单元测试 Skill

2.1 准备工作

首先,创建 Skill 目录:

mkdir -p .claude/skills/java-unit-test 

2.2 编写 SKILL.md

java-unit-test 文件夹下创建 SKILL.md 文件。这是 Skill 的核心文件,采用 YAML Frontmatter + Markdown 正文 的格式。

YAML 元数据

文件开头必须包含 YAML 格式的元数据:

---name: java-unit-test description: 为 Java 项目生成自动化单元测试,基于 JUnit 5 和 Mockito 框架。 当用户要求编写单元测试、测试用例或进行测试覆盖时使用。 allowed-tools: Read, Bash version: 1.0.0 ---

字段说明

  • name:技能名称(1-64 字符,只能包含小写字母、数字和连字符)
  • description:功能描述和使用时机(1-1024 字符,必须包含关键词)
  • allowed-tools:允许使用的工具(如 Read 读取文件、Bash 执行命令)
  • version:版本号(可选)
主体内容结构

Markdown 主体包含技能的具体指令,建议采用以下结构:

## 触发条件 (说明什么情况下激活此技能) ## 前置检查清单 (在生成测试前必须执行的检查) ## 测试代码生成规范 (详细的代码生成规则) ## 测试用例设计原则 (测试覆盖策略、场景设计) ## 最佳实践清单 (命名、结构、断言等规范) ## 常见问题与解决方案 (边界情况、错误处理) 

2.3 核心:触发条件与前置检查

触发条件定义
## 触发条件 当用户提出以下需求时激活此技能: - "帮我写单元测试" - "生成测试用例" - "为这个类写测试" - "测试覆盖率" - "单元测试" - "junit test" - "mockito test" 
前置检查清单
## 前置检查清单 在生成测试代码前,必须执行以下检查: ### 1. 识别被测类 - 读取用户提供的 Java 源文件 - 如果未提供,询问用户提供被测类的完整路径 ### 2. 分析类结构 - 识别所有 public 方法(包括构造方法) - 记录方法的参数、返回值类型、异常声明 - 识别依赖的其他类(用于 Mock) ### 3. 确定测试框架 - 默认使用 JUnit 5 (Jupiter) - Mock 框架使用 Mockito 3.x 或更高版本 

关键点:前置检查确保 Claude 在生成代码前,充分理解被测类的结构,避免生成无效的测试代码。

三、测试代码生成规范详解

3.1 测试类命名规范

## 测试类命名规范 测试类名称:`[被测类名]Test.java` // 示例 被测类:UserService.java 测试类:UserServiceTest.java 

3.2 导入声明规范

按以下顺序组织导入:

// 1. JUnit 5 核心导入importorg.junit.jupiter.api.*;importstaticorg.junit.jupiter.api.Assertions.*;// 2. Mockito 导入importorg.mockito.Mock;importorg.mockito.InjectMocks;importstaticorg.mockito.Mockito.*;importstaticorg.mockito.ArgumentMatchers.*;// 3. 被测类导入importcom.yourpackage.UserService;// 4. 其他依赖导入importjava.util.List;

优势:清晰的导入顺序提升代码可读性,符合团队规范。

3.3 测试类结构模板

这是 Skill 的核心部分,定义了测试类的标准结构:

@ExtendWith(MockitoExtension.class)@DisplayName("UserService 单元测试")classUserServiceTest{// ========== Mock 对象声明 ==========@MockprivateUserRepository userRepository;@MockprivateEmailService emailService;@InjectMocksprivateUserService userService;// ========== 测试前置条件 ==========@BeforeEachvoidsetUp(){// 初始化测试数据}@AfterEachvoidtearDown(){// 清理资源}// ========== 测试方法 ==========@Test@DisplayName("创建用户 - 成功场景")voidcreateUser_Success(){// Given - 准备测试数据UserDTO userDTO =newUserDTO("[email protected]","password123");User savedUser =newUser(1L,"[email protected]","password123");when(userRepository.existsByEmail(anyString())).thenReturn(false);when(userRepository.save(any(User.class))).thenReturn(savedUser);// When - 执行被测方法Long userId = userService.createUser(userDTO);// Then - 验证结果assertNotNull(userId);assertEquals(1L, userId);// 验证 Mock 调用verify(userRepository).existsByEmail("[email protected]");verify(userRepository).save(any(User.class));verify(emailService).sendWelcomeEmail(eq("[email protected]"));}@Test@DisplayName("创建用户 - 邮箱已存在抛出异常")voidcreateUser_EmailAlreadyExists_ThrowsException(){// GivenUserDTO userDTO =newUserDTO("[email protected]","password123");when(userRepository.existsByEmail(anyString())).thenReturn(true);// When & ThenassertThrows(UserAlreadyExistsException.class,()-> userService.createUser(userDTO));verify(userRepository,never()).save(any(User.class));verify(emailService,never()).sendWelcomeEmail(anyString());}}

结构亮点

  1. 分区清晰:Mock 对象、Setup、测试方法明确分区
  2. Given-When-Then:测试方法内部采用 G-W-T 模式,逻辑清晰
  3. DisplayName:使用 @DisplayName 注解增强可读性
  4. Mock 验证:不仅验证返回值,还验证 Mock 调用情况

3.4 Mock 对象配置规范

完整的 Mock 配置是 Skill 的核心价值之一:

### 常用 Mock 方法 // 返回指定值 when(mock.someMethod(anyString())).thenReturn("result"); // 抛出异常 when(mock.someMethod(anyString())).thenThrow(new RuntimeException()); // 链式调用 when(mock.someMethod()) .thenReturn("first") .thenReturn("second") .thenThrow(new Exception()); // 真实调用(部分 mock) when(mock.someMethod()).thenCallRealMethod(); // 无返回值方法 doNothing().when(mock).voidMethod(anyString()); // 抛出异常(void 方法) doThrow(new RuntimeException()).when(mock).voidMethod(); // 按参数类型匹配 when(mock.method(anyString(), anyInt())).thenReturn(result); when(mock.method(eq("specific"), anyInt())).thenReturn(result); 
验证 Mock 调用
// 验证调用次数verify(mock).someMethod();// 调用 1 次verify(mock,times(2)).someMethod();// 调用 2 次verify(mock,never()).someMethod();// 从未调用verify(mock,atLeastOnce()).someMethod();// 至少调用 1 次verify(mock,atMost(3)).someMethod();// 最多调用 3 次// 验证调用顺序InOrder inOrder =inOrder(mock1, mock2); inOrder.verify(mock1).firstMethod(); inOrder.verify(mock2).secondMethod();// 验证参数verify(mock).someMethod(eq("specific"));verify(mock).someMethod(argThat(argument -> argument.length()>5));

优势:统一的 Mock 配置规范,避免团队成员使用不同的方式,提升代码一致性。

3.5 测试数据构建规范

使用 Builder 模式(推荐)
// 如果被测类有 BuilderUser user =User.builder().id(1L).email("[email protected]").password("encodedPassword").status(UserStatus.ACTIVE).build();// 或者使用测试专用的 BuilderUser user =TestUserBuilder.aUser().withId(1L).withEmail("[email protected]").build();
直接构造
User user =newUser(); user.setId(1L); user.setEmail("[email protected]"); user.setPassword("encodedPassword"); user.setStatus(UserStatus.ACTIVE);

四、测试用例设计原则

4.1 测试覆盖策略

为每个方法设计以下测试场景:

场景类型说明示例
正常场景最常见的有效输入用户创建成功
边界场景边界值、临界条件列表为空、单元素列表
异常场景预期的异常情况用户已存在、参数无效
空值场景null 或空字符串email 为 null
业务规则特定业务逻辑约束用户年龄限制、状态转换

示例:为 createUser() 方法设计测试场景

@TestvoidcreateUser_WithValidData_Success(){}@TestvoidcreateUser_WithDuplicateEmail_ThrowsException(){}@TestvoidcreateUser_WithNullEmail_ThrowsException(){}@TestvoidcreateUser_WithEmptyPassword_ThrowsException(){}

4.2 测试金字塔

遵循测试金字塔原则:

 /\ / \ E2E Tests (少量) /____\ / \ Integration Tests (适量) /________\ / \ Unit Tests (大量) /____________\ 
  • 单元测试:70-80%,快速、独立、覆盖核心逻辑
  • 集成测试:20-25%,验证组件协作
  • 端到端测试:5-10%,验证完整流程

4.3 测试覆盖率目标

类型覆盖率目标
行覆盖率≥ 80%
分支覆盖率≥ 70%
方法覆盖率100%

五、在 Claude Code 中使用 Skill

5.1 自动触发(推荐)

直接在 Claude Code 中提出需求:

帮我为 UserService.java 写单元测试 

Claude 会自动:

  1. 读取你的 Java 源文件
  2. 分析类结构和依赖
  3. 按照 Skill 规范生成完整的测试代码

5.2 手动触发

如果自动判定未命中,使用:

/java-unit-test 

然后描述你的需求。

5.3 热重载

从 Claude Code v2.1.1 开始,修改 SKILL.md 后无需重启,立即生效。这意味着你可以:

  1. 修改测试规范
  2. 保存文件
  3. 立即在 Claude Code 中测试新规范

六、实战案例

6.1 被测类示例

假设你有一个 UserService.java

@ServicepublicclassUserService{@AutowiredprivateUserRepository userRepository;@AutowiredprivateEmailService emailService;@AutowiredprivatePasswordEncoder passwordEncoder;publicLongcreateUser(UserDTO userDTO){if(userDTO ==null){thrownewIllegalArgumentException("用户信息不能为空");}if(userRepository.existsByEmail(userDTO.getEmail())){thrownewUserAlreadyExistsException("邮箱已存在");}User user =newUser(); user.setEmail(userDTO.getEmail()); user.setPassword(passwordEncoder.encode(userDTO.getPassword()));User savedUser = userRepository.save(user); emailService.sendWelcomeEmail(user.getEmail());return savedUser.getId();}publicUsergetUserById(Long id){if(id ==null){thrownewIllegalArgumentException("用户 ID 不能为空");}return userRepository.findById(id).orElseThrow(()->newUserNotFoundException("用户不存在"));}publicList<User>getAllUsers(){return userRepository.findAll();}}

6.2 Claude 生成的测试代码

当你说"为 UserService 写单元测试"时,Claude 会生成:

@ExtendWith(MockitoExtension.class)@DisplayName("UserService 单元测试")classUserServiceTest{@MockprivateUserRepository userRepository;@MockprivateEmailService emailService;@MockprivatePasswordEncoder passwordEncoder;@InjectMocksprivateUserService userService;@Test@DisplayName("创建用户 - 成功场景")voidcreateUser_Success(){// GivenUserDTO userDTO =newUserDTO("[email protected]","password123");User savedUser =newUser(1L,"[email protected]","encoded");when(userRepository.existsByEmail(anyString())).thenReturn(false);when(passwordEncoder.encode(anyString())).thenReturn("encoded");when(userRepository.save(any(User.class))).thenReturn(savedUser);// WhenLong userId = userService.createUser(userDTO);// ThenassertNotNull(userId);assertEquals(1L, userId);verify(userRepository).existsByEmail("[email protected]");verify(passwordEncoder).encode("password123");verify(userRepository).save(any(User.class));verify(emailService).sendWelcomeEmail("[email protected]");}@Test@DisplayName("创建用户 - 邮箱已存在抛出异常")voidcreateUser_EmailAlreadyExists_ThrowsException(){// GivenUserDTO userDTO =newUserDTO("[email protected]","password123");when(userRepository.existsByEmail(anyString())).thenReturn(true);// When & ThenassertThrows(UserAlreadyExistsException.class,()-> userService.createUser(userDTO));verify(userRepository,never()).save(any(User.class));verify(emailService,never()).sendWelcomeEmail(anyString());}@Test@DisplayName("创建用户 - 参数为空抛出异常")voidcreateUser_NullParameter_ThrowsException(){// When & ThenassertThrows(IllegalArgumentException.class,()-> userService.createUser(null));verify(userRepository,never()).existsByEmail(anyString());}@Test@DisplayName("根据 ID 获取用户 - 成功场景")voidgetUserById_Success(){// GivenLong userId =1L;User user =newUser(userId,"[email protected]","encoded");when(userRepository.findById(userId)).thenReturn(Optional.of(user));// WhenUser result = userService.getUserById(userId);// ThenassertNotNull(result);assertEquals(userId, result.getId());assertEquals("[email protected]", result.getEmail());verify(userRepository).findById(userId);}@Test@DisplayName("根据 ID 获取用户 - 用户不存在抛出异常")voidgetUserById_UserNotFound_ThrowsException(){// GivenLong userId =999L;when(userRepository.findById(userId)).thenReturn(Optional.empty());// When & ThenassertThrows(UserNotFoundException.class,()-> userService.getUserById(userId));verify(userRepository).findById(userId);}@Test@DisplayName("获取所有用户 - 返回空列表")voidgetAllUsers_ReturnsEmptyList(){// Givenwhen(userRepository.findAll()).thenReturn(Collections.emptyList());// WhenList<User> result = userService.getAllUsers();// ThenassertNotNull(result);assertTrue(result.isEmpty());verify(userRepository).findAll();}@Test@DisplayName("获取所有用户 - 返回用户列表")voidgetAllUsers_ReturnsUserList(){// GivenList<User> users =Arrays.asList(newUser(1L,"[email protected]","encoded"),newUser(2L,"[email protected]","encoded"));when(userRepository.findAll()).thenReturn(users);// WhenList<User> result = userService.getAllUsers();// ThenassertNotNull(result);assertEquals(2, result.size());verify(userRepository).findAll();}}

生成结果分析

特性说明
完整覆盖8 个测试方法,覆盖所有 public 方法和异常场景
结构统一所有测试都遵循 Given-When-Then 结构
Mock 完整所有依赖都使用 Mock,隔离外部依赖
断言精准每个测试都有清晰的断言和 Mock 验证
命名清晰测试方法名称描述完整场景

6.3 进阶:参数化测试

对于多组输入输出的场景,使用参数化测试:

@ParameterizedTest@DisplayName("密码强度校验")@MethodSource("providePasswords")voidvalidatePassword_PasswordStrength(String password,boolean expected){boolean result = userService.validatePassword(password);assertEquals(expected, result);}privatestaticStream<Arguments>providePasswords(){returnStream.of(Arguments.of("weak",false),Arguments.of("Strong123!",true),Arguments.of("TooShort1",false),Arguments.of("NoNumberHere!",false));}

优势:用更少的代码覆盖更多测试场景。

七、最佳实践清单

7.1 测试命名清单

  • 测试方法名称清晰描述测试场景
  • 使用 @DisplayName 增强可读性
  • 测试类命名为 [被测类]Test

7.2 测试结构清单

  • 遵循 Given-When-Then 结构
  • Mock 对象在类级别声明
  • 使用 @BeforeEach 初始化测试数据
  • 使用 @AfterEach 清理资源

7.3 断言清单

  • 每个测试至少有一个断言
  • 使用具体的断言方法(如 assertEquals 而非 assertTrue
  • 断言失败时信息清晰
  • 必要时使用 assertAll 进行批量断言

7.4 Mock 清单

  • Mock 验证覆盖关键依赖
  • 使用 any()eq() 精确匹配参数
  • 验证 Mock 调用次数和参数
  • 避免过度 Mock(如 private 方法)

7.5 性能清单

  • 测试执行时间合理(单个测试 < 1秒)
  • 使用 @Timeout 防止无限等待
  • 避免不必要的数据库操作

八、常见问题与解决方案

8.1 静态方法 Mock

对于静态方法,使用 Mockito 3.4+ 的 inline mock maker:

@ExtendWith(MockitoExtension.class)classStaticMethodTest{@TestvoidmockStaticMethod(){try(MockedStatic<StringUtils> mocked =mockStatic(StringUtils.class)){ mocked.when(()->StringUtils.isEmpty(anyString())).thenReturn(true);boolean result =StringUtils.isEmpty("test");assertTrue(result);}}}

8.2 Final 类 Mock

Mock 默认不能 Mock final 类,需要配置:

// 在 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker # 内容: mock-maker-inline 

8.3 私有方法测试

不推荐直接测试私有方法。建议:

  1. 测试 public 方法间接覆盖私有方法
  2. 使用反射(仅作为最后手段)
@TestvoidtestPrivateMethod()throwsException{Method method =UserService.class.getDeclaredMethod("privateMethod",String.class); method.setAccessible(true);Object result = method.invoke(userService,"test");assertNotNull(result);}

九、团队协作与持续优化

9.1 团队共享 Skill

将 Skill 目录提交到版本控制系统:

gitadd .claude/skills/java-unit-test/ git commit -m "feat: 添加 Java 单元测试自动化 Skill"git push 

团队成员拉取后,即可使用统一的测试规范。

9.2 持续迭代

根据团队反馈持续优化 Skill:

  1. 收集反馈:团队成员在使用过程中提出改进建议
  2. 分析问题:识别测试代码中的常见问题
  3. 更新 Skill:将解决方案固化到 Skill 中
  4. 版本控制:使用 Git 管理版本迭代

9.3 扩展 Skill

可以基于此 Skill 扩展其他测试场景:

  • 集成测试 Skill
  • 端到端测试 Skill
  • 性能测试 Skill
  • 安全测试 Skill

十、总结

核心价值

通过创建 Java 单元测试自动化 Skill,我们获得了:

维度提升
开发效率测试代码生成时间减少 70% 以上
代码质量统一的测试规范,覆盖率显著提升
团队协作避免风格分歧,降低 review 成本
知识沉淀测试经验固化,新人快速上手
持续优化集中管理,随时迭代改进

关键要点

  1. Skills 是什么:给 AI 的操作手册,打包经验和流程
  2. 核心文件SKILL.md(必需)+ 可选的辅助资源
  3. 快速上手:创建目录、编写 SKILL.md、立即可用
  4. 最佳实践:原子化、克制、渐进披露、可测试
  5. 团队协作:版本控制、持续迭代、知识沉淀

下一步建议

  1. 创建你的第一个 Skill:从 Java 单元测试开始
  2. 实际应用:在真实项目中使用,积累经验
  3. 团队推广:分享给团队成员,统一规范
  4. 持续优化:根据反馈迭代,完善 Skill
  5. 扩展应用:为其他场景创建 Skills(如代码审查、文档生成)

最后的话

Skills > Agents —— 相比一次性的对话,持续积累的 Skills 才是真正的生产力资产。

在 AI 时代,真正的竞争力不在于你用了多少次 AI,而在于你是否能够将经验固化、复用、持续优化。Claude Skills 正是这样一款能够帮助你实现这一目标的利器。

从今天开始,创建你的第一个 Skill,让 AI 真正成为你的生产力工具吧!


附录:完整 Skill 代码示例

完整的 SKILL.md 文件已开源,包含:

  • ✅ 完整的前置检查清单
  • ✅ 详细的测试代码生成规范
  • ✅ Mock 对象配置和验证方法
  • ✅ 测试用例设计原则
  • ✅ 最佳实践清单
  • ✅ 常见问题与解决方案

你可以直接使用或根据团队需求进行定制。

参考资源


作者:猿来如此呀
发布时间:2026-01-21
阅读时间:约 15 分钟
难度等级:中级
联系方式:微信:moyu19950519

Read more

C++ 多线程同步之互斥锁(mutex)实战

C++ 多线程同步之互斥锁(mutex)实战

C++ 多线程同步之互斥锁(mutex)实战 💡 学习目标:掌握 C++ 标准库中互斥锁的基本用法,理解多线程同步的核心原理,能够解决多线程环境下的资源竞争问题。 💡 学习重点:std::mutex 与 std::lock_guard 的使用、死锁的产生原因及规避方法、实际场景中的同步案例实现。 48.1 多线程同步的必要性 在多线程编程中,当多个线程同时访问共享资源时,会出现资源竞争问题。 例如两个线程同时对同一个变量进行读写操作,会导致最终结果与预期不符。 这种问题被称为线程安全问题,而解决该问题的核心就是线程同步。 ⚠️ 注意事项:线程不同步会引发数据竞争,造成程序运行结果不可预测,甚至导致程序崩溃。 举个简单的反例,两个线程同时对全局变量 count 进行自增操作: #include<iostream>#include<thread>usingnamespace std;int count

By Ne0inhk
C++ 多态:面向对象的动态行为核心机制

C++ 多态:面向对象的动态行为核心机制

C++ 多态:面向对象的动态行为核心机制 💡 学习目标:掌握多态的概念与分类,理解虚函数的作用原理,能够熟练使用多态实现程序的动态行为扩展。 💡 学习重点:静态多态与动态多态的区别、虚函数的定义与使用、纯虚函数与抽象类、多态的实战应用场景。 一、多态的概念与分类 ✅ 结论:多态是 C++ 面向对象三大特性之一,指同一行为在不同对象上表现出不同的形态,核心是“一个接口,多种实现”。 多态主要分为两大类,二者的实现原理和触发时机截然不同: 1. 静态多态:编译阶段确定调用关系,也叫编译时多态,实现方式包括函数重载和运算符重载 2. 动态多态:运行阶段确定调用关系,也叫运行时多态,实现方式是虚函数 + 基类指针/引用 生活中的多态示例:同样是“动物叫”这个行为,猫的叫声是“喵喵喵”,狗的叫声是“汪汪汪”,不同动物对象表现出不同的行为形态。 二、静态多态:编译时确定的多态性 💡 静态多态的调用关系在编译阶段就已确定,编译器会根据参数列表的差异匹配对应的函数。

By Ne0inhk
飞算JavaAI需求转SpringBoot项目沉浸式体验

飞算JavaAI需求转SpringBoot项目沉浸式体验

文章目录 * 一、引言:从手撸代码到智能开发的蜕变 * 二、智能引导:六步实现需求到代码的无缝转换 * 1. 需求精准解析 * 2. 接口智能设计 * 3. 表结构可视化设计 * 4. 业务逻辑编排 * 5. 代码预览与确认 * 6. 一键生成可运行工程(图6) * 三、效率与质量的双重跃升:数据见证变革 * 1. 开发效率对比 * 2. 代码质量对比 * 3. 性能表现 * 四、与同类产品的差异化优势 * 1. 与Cursor的对比 * 2. 与通义灵码的对比 * 3. 与传统低代码平台的对比 * 五、结语:重构Java开发的未来图景 一、引言:从手撸代码到智能开发的蜕变 作为一名深耕Java开发多年的工程师,我曾无数次在需求变更、代码重构的泥潭中挣扎。传统开发模式下,从需求分析到Spring Boot项目落地,往往需要耗费数周时间,

By Ne0inhk
JDK 1.8安装教程(附百度网盘下载地址)

JDK 1.8安装教程(附百度网盘下载地址)

Java开发环境的搭建是学习Java编程的第一步,而JDK 1.8(Java 8)作为长期支持版本(LTS),因其稳定性和广泛兼容性,至今仍是企业和开发者的首选。本文将手把手教你如何在Windows系统中安装和配置JDK 1.8,并附官方下载与网盘备用地址。 一、JDK 1.8下载 1. 官方下载(推荐) 1. 访问Oracle官网下载页面: Oracle JDK 8下载地址 注意:需注册Oracle账号并登录后才能下载。 2. 根据系统选择对应版本: * Windows系统选择 jdk-8uXXX-windows-x64.exe(XXX为版本号) * macOS系统选择 jdk-8uXXX-macosx-x64.dmg 2. 百度网盘备份(备选) 若官网下载困难,可使用以下网盘地址: 链接: https://pan.baidu.com/s/1ov7bWVxu82Bs7FaPSB4DkQ?pwd=

By Ne0inhk