Java 开发规范
参考来源: Google Java Style Guide、阿里巴巴 Java 开发手册
工具链
bash
1# Maven
2mvn clean compile # 编译
3mvn test # 运行测试
4mvn verify # 运行所有检查
5
6# Gradle
7./gradlew build # 构建
8./gradlew test # 运行测试
命名约定
| 类型 | 规则 | 示例 |
|---|
| 包名 | 全小写,域名反转 | com.example.project |
| 类名 | 大驼峰,名词/名词短语 | UserService, HttpClient |
| 方法名 | 小驼峰,动词开头 | findById, isValid |
| 常量 | 全大写下划线分隔 | MAX_RETRY_COUNT |
| 布尔返回值 | is/has/can 前缀 | isActive(), hasPermission() |
类成员顺序
java
1public class Example {
2 // 1. 静态常量
3 public static final String CONSTANT = "value";
4
5 // 2. 静态变量
6 private static Logger logger = LoggerFactory.getLogger(Example.class);
7
8 // 3. 实例变量
9 private Long id;
10
11 // 4. 构造函数
12 public Example() { }
13
14 // 5. 静态方法
15 public static Example create() { return new Example(); }
16
17 // 6. 实例方法(公共 → 私有)
18 public void doSomething() { }
19 private void helperMethod() { }
20
21 // 7. getter/setter(或使用 Lombok)
22}
DTO/VO 类规范
| 规则 | 说明 |
|---|
| ❌ 禁止手写 getter/setter | DTO、VO、Request、Response 类一律使用 Lombok |
✅ 使用 @Data | 普通 DTO |
✅ 使用 @Value | 不可变 DTO |
✅ 使用 @Builder | 字段较多时配合使用 |
⚠️ Entity 类慎用 @Data | JPA Entity 的 equals/hashCode 会影响 Hibernate 代理 |
java
1// ❌ 手写 getter/setter
2public class UserDTO {
3 private Long id;
4 private String name;
5 public Long getId() { return id; }
6 public void setId(Long id) { this.id = id; }
7 // ... 大量样板代码
8}
9
10// ✅ 使用 Lombok
11@Data
12public class UserDTO {
13 private Long id;
14 private String name;
15}
批量查询规范
| 规则 | 说明 |
|---|
| ❌ 禁止 IN 子句超过 500 个参数 | SQL 解析开销大,执行计划不稳定 |
| ✅ 超过时分批查询 | 每批 500,合并结果 |
| ✅ 封装通用工具方法 | 避免每处手写分批逻辑 |
java
1// ❌ 1700 个 ID 一次查询
2List<User> users = userRepository.findByIdIn(allIds); // IN 子句过长
3
4// ✅ 分批查询工具方法
5public static <T, R> List<R> batchQuery(List<T> params, int batchSize,
6 Function<List<T>, List<R>> queryFn) {
7 List<R> result = new ArrayList<>();
8 for (int i = 0; i < params.size(); i += batchSize) {
9 List<T> batch = params.subList(i, Math.min(i + batchSize, params.size()));
10 result.addAll(queryFn.apply(batch));
11 }
12 return result;
13}
14
15// 使用
16List<User> users = batchQuery(allIds, 500, ids -> userRepository.findByIdIn(ids));
N+1 查询防范
| 规则 | 说明 |
|---|
| ❌ 禁止循环内调用 Repository/Mapper | stream/forEach/for 内每次迭代触发一次查询 |
| ✅ 循环外批量查询,结果转 Map | 查询次数从 N 降为 1(或 distinct 数) |
java
1// ❌ N+1:循环内逐行查询 count
2records.forEach(record -> {
3 long count = deviceRepo.countByDeviceId(record.getDeviceId()); // 每条触发一次查询
4 record.setDeviceCount(count);
5});
6
7// ✅ 循环外批量查询 + Map 查找
8List<String> deviceIds = records.stream()
9 .map(Record::getDeviceId).distinct().collect(Collectors.toList());
10Map<String, Long> countMap = deviceRepo.countByDeviceIdIn(deviceIds).stream()
11 .collect(Collectors.toMap(CountDTO::getDeviceId, CountDTO::getCount));
12records.forEach(r -> r.setDeviceCount(countMap.getOrDefault(r.getDeviceId(), 0L)));
常见 N+1 场景及修复模式:
| 场景 | 循环内(❌) | 循环外(✅) |
|---|
| count | repo.countByXxx(id) | repo.countByXxxIn(ids) → Map<id, count> |
| findById | repo.findById(id) | repo.findByIdIn(ids) → Map<id, entity> |
| exists | repo.existsByXxx(id) | repo.findXxxIn(ids) → Set<id> + set.contains() |
并发安全规范
| 规则 | 说明 |
|---|
| ❌ 禁止 read-modify-write | 先读余额再写回,并发下丢失更新 |
| ❌ 禁止 check-then-act 无兜底 | 先检查再操作,并发下条件失效 |
| ✅ 使用原子更新 SQL | UPDATE SET balance = balance + :delta WHERE id = :id |
| ✅ 或使用乐观锁 | @Version 字段 + 重试机制 |
| ✅ 唯一索引兜底 | 防重复插入的最后防线 |
java
1// ❌ read-modify-write 竞态条件
2PointsAccount account = accountRepo.findById(id);
3account.setBalance(account.getBalance() + points); // 并发时丢失更新
4accountRepo.save(account);
5
6// ✅ 方案一:原子更新 SQL
7@Modifying
8@Query("UPDATE PointsAccount SET balance = balance + :points WHERE id = :id")
9int addBalance(@Param("id") Long id, @Param("points") int points);
10
11// ✅ 方案二:乐观锁
12@Version
13private Long version; // Entity 中添加版本字段
java
1// ❌ check-then-act 无兜底(并发下可能重复结算)
2if (!rewardRepo.existsByTenantIdAndPeriod(tenantId, period)) {
3 rewardRepo.save(new RankingReward(...));
4}
5
6// ✅ 唯一索引兜底 + 异常捕获
7// DDL: UNIQUE INDEX uk_tenant_period (tenant_id, ranking_type, period, rank_position)
8try {
9 rewardRepo.save(new RankingReward(...));
10} catch (DataIntegrityViolationException e) {
11 log.warn("重复结算已被唯一索引拦截: tenantId={}, period={}", tenantId, period);
12}
异常处理
java
1// ✅ 好:捕获具体异常,添加上下文
2try {
3 user = userRepository.findById(id);
4} catch (DataAccessException e) {
5 throw new ServiceException("Failed to find user: " + id, e);
6}
7
8// ✅ 好:资源自动关闭
9try (InputStream is = new FileInputStream(file)) {
10 // 使用资源
11}
12
13// ❌ 差:捕获过宽
14catch (Exception e) { e.printStackTrace(); }
空值处理
java
1// ✅ 使用 Optional
2public Optional<User> findById(Long id) {
3 return userRepository.findById(id);
4}
5
6// ✅ 参数校验
7public void updateUser(User user) {
8 Objects.requireNonNull(user, "user must not be null");
9}
10
11// ✅ 安全的空值处理
12String name = Optional.ofNullable(user)
13 .map(User::getName)
14 .orElse("Unknown");
并发编程
java
1// ✅ 使用 ExecutorService
2ExecutorService executor = Executors.newFixedThreadPool(10);
3Future<Result> future = executor.submit(() -> doWork());
4
5// ✅ 使用 CompletableFuture
6CompletableFuture<User> future = CompletableFuture
7 .supplyAsync(() -> findUser(id))
8 .thenApply(user -> enrichUser(user));
9
10// ❌ 差:直接创建线程
11new Thread(() -> doWork()).start();
测试规范 (JUnit 5)
java
1class UserServiceTest {
2 @Test
3 @DisplayName("根据 ID 查找用户 - 用户存在时返回用户")
4 void findById_whenUserExists_returnsUser() {
5 // given
6 when(userRepository.findById(1L)).thenReturn(Optional.of(expected));
7
8 // when
9 Optional<User> result = userService.findById(1L);
10
11 // then
12 assertThat(result).isPresent();
13 assertThat(result.get().getName()).isEqualTo("test");
14 }
15}
Spring Boot 规范
java
1// ✅ 构造函数注入
2@Service
3@RequiredArgsConstructor
4public class UserService {
5 private final UserRepository userRepository;
6 private final EmailService emailService;
7}
8
9// ✅ REST Controller
10@RestController
11@RequestMapping("/api/users")
12public class UserController {
13 @GetMapping("/{id}")
14 public ResponseEntity<UserDto> findById(@PathVariable Long id) {
15 return userService.findById(id)
16 .map(ResponseEntity::ok)
17 .orElse(ResponseEntity.notFound().build());
18 }
19}
Auth Filter 降级原则
| 规则 | 说明 |
|---|
| ✅ optional-auth 路径遇到无效/过期/不完整 token 时降级为匿名访问 | 不应返回 401/403 |
| ❌ 禁止部分凭证用户体验差于匿名用户 | 如:临时 token 在公开接口返回 403 |
输入校验规范
| 规则 | 说明 |
|---|
❌ 禁止 @RequestBody 不加 @Valid | 所有请求体必须校验 |
| ✅ DTO 字段加约束注解 | @NotBlank、@Size、@Pattern 等 |
| ✅ 数值字段加范围约束 | @Min、@Max、@Positive 等 |
| ✅ 分页参数加上限 | size 必须 @Max(100) 防止大量查询 |
| ✅ 枚举/状态字段白名单校验 | 自定义校验器或 @Pattern |
常见 DTO 字段校验速查:
| 字段类型 | 必须注解 | 说明 |
|---|
| 数量 quantity | @NotNull @Min(1) | 防止 0 或负数(负数可导致反向操作) |
| 金额 amount/price | @NotNull @Positive | 或 @DecimalMin("0.01") |
| 分页 size | @Min(1) @Max(100) | 防止 size=999999 拖垮数据库 |
| 分页 page | @Min(1) | 页码从 1 开始 |
| 百分比 rate | @Min(0) @Max(100) | 视业务定义范围 |
java
1// ❌ 无校验,任意输入直接进入业务逻辑
2@PostMapping("/ship")
3public Result ship(@RequestBody ShippingRequest request) { ... }
4
5// ✅ 完整校验
6@PostMapping("/ship")
7public Result ship(@RequestBody @Valid ShippingRequest request) { ... }
8
9public record ShippingRequest(
10 @NotNull Long orderId,
11 @NotBlank @Size(max = 500) String shippingInfo,
12 @Pattern(regexp = "pending|shipped|delivered") String giftStatus
13) {}
14
15// ❌ quantity 只有 @NotNull,负数会导致 Redis DECRBY 反向加库存
16public record CreateOrderRequest(
17 @NotNull Integer quantity // 可提交 0 或负数
18) {}
19
20// ✅ 数量必须 >= 1
21public record CreateOrderRequest(
22 @NotNull @Min(1) Integer quantity
23) {}
24
25// ❌ 分页无上限,用户可传 size=999999
26@GetMapping("/orders")
27public Result list(@RequestParam int page, @RequestParam int size) { ... }
28
29// ✅ 分页参数加约束
30@GetMapping("/orders")
31public Result list(@RequestParam @Min(1) int page,
32 @RequestParam @Min(1) @Max(100) int size) { ... }
性能优化
| 陷阱 | 解决方案 |
|---|
| N+1 查询 | 见「N+1 查询防范」章节 |
| 循环拼接字符串 | 使用 StringBuilder |
| 频繁装箱拆箱 | 使用原始类型流 |
| 未指定集合初始容量 | new ArrayList<>(size) |
日志规范
java
1// ✅ 参数化日志
2log.debug("Finding user by id: {}", userId);
3log.info("User {} logged in successfully", username);
4log.error("Failed to process order {}", orderId, exception);
5
6// ❌ 差:字符串拼接
7log.debug("Finding user by id: " + userId);
详细参考
| 文件 | 内容 |
|---|
references/java-style.md | 命名约定、异常处理、Spring Boot、测试规范 |
references/collections.md | 不可变集合(Guava)、字符串分割 |
references/concurrency.md | 线程池配置、CompletableFuture 超时 |
references/concurrency-db-patterns.md | Get-Or-Create 并发、N+1 防范、原子更新、Redis+DB 一致性 |
references/code-patterns.md | 卫语句、枚举优化、策略工厂模式 |
references/date-time.md | 日期加减、账期计算、禁止月末对齐 |
📋 本回复遵循:java-dev - [具体章节]