Java 去样板代码 Cheatsheet
面向 Java 后端开发的 modern Java / engineering tricks。目标不是炫技,而是把那些“必须写、但信息密度很低”的样板代码压掉,把注意力留给业务本身。
目录
- record:干掉 DTO / VO / Request 样板
- var:减少局部变量类型噪音
- Optional:减少 null if-else
- Stream API:声明式集合处理
- 方法引用
:::让 lambda 更干净
- switch 表达式:替代老式 switch
- Text Block:多行字符串不再丑
- Lombok:自动生成常见样板
- Bean Validation:参数校验声明式化
- MapStruct:对象转换自动化
- MyBatis-Plus:减少简单 CRUD / 条件查询代码
- 枚举承载行为:替代散落的 if/switch
- Builder:解决长构造器 / 多可选参数
- 泛型统一返回体:减少重复响应结构
- 策略模式:把“分支型业务”从 if-else 中解放出来
- 建议学习顺序
- 高频组合拳
1. record:干掉 DTO / VO / Request 样板
替代什么
替代那种只负责装数据的 POJO:字段、构造器、getter、equals/hashCode/toString。
传统写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class LoginRequest { private String username; private String password;
public LoginRequest(String username, String password) { this.username = username; this.password = password; }
public String getUsername() { return username; }
public String getPassword() { return password; } }
|
record 写法
1 2
| public record LoginRequest(String username, String password) { }
|
使用方式
1 2 3
| LoginRequest request = new LoginRequest("Gilbert", "123456"); System.out.println(request.username()); System.out.println(request.password());
|
适用场景
- Request DTO
- Response VO
- MQ 消息体
- 查询结果投影
- 只表达“某一刻数据事实”的对象
坑点
record 默认更偏 immutable,不能随手 set
- 不适合“流程中不断补字段”的对象
- 访问器不是
getXxx(),而是 xxx()
2. var:减少局部变量类型噪音
替代什么
替代那些右侧类型已经非常明显、但左侧又写一遍很啰嗦的局部变量声明。
传统写法
1 2
| Map<Long, List<User>> userMap = users.stream() .collect(Collectors.groupingBy(User::getDeptId));
|
var 写法
1 2
| var userMap = users.stream() .collect(Collectors.groupingBy(User::getDeptId));
|
适用场景
不推荐场景
右边看不出类型时,会伤害可读性。
经验
var 是为了让视觉重心落到逻辑,而不是为了显得高级。
3. Optional:减少 null if-else
替代什么
替代多层 null 判断和默认值处理。
传统写法
1 2 3 4 5 6 7 8
| User user = userService.getById(id); if (user != null) { String name = user.getName(); if (name != null) { return name.trim(); } } return "unknown";
|
Optional 写法
1 2 3 4
| return Optional.ofNullable(userService.getById(id)) .map(User::getName) .map(String::trim) .orElse("unknown");
|
高频写法
1 2 3 4
| Optional.ofNullable(obj) .map(...) .filter(...) .orElse(defaultValue);
|
适用场景
- 可能为空的返回值
- 多层取值
- 默认值 fallback
- 查不到则抛异常
例子:查不到抛异常
1 2
| User user = Optional.ofNullable(userService.getById(id)) .orElseThrow(() -> new RuntimeException("用户不存在"));
|
坑点
- 不要把
Optional 当成实体类 / DTO 字段类型到处用
- 不要写成复杂 monad 表演赛,超过可读性就收手
4. Stream API:声明式集合处理
替代什么
替代“遍历 + 判断 + 转换 + 收集”的大量 for 循环。
传统写法
1 2 3 4 5 6
| List<Long> ids = new ArrayList<>(); for (User user : users) { if (user.getAge() >= 18) { ids.add(user.getId()); } }
|
Stream 写法
1 2 3 4
| List<Long> ids = users.stream() .filter(user -> user.getAge() >= 18) .map(User::getId) .toList();
|
高频模板
1 2 3 4 5 6
| list.stream() .filter(...) .map(...) .distinct() .sorted() .toList();
|
高频收集器
1 2 3 4 5 6 7
| Collectors.toList() Collectors.toSet() Collectors.toMap(...) Collectors.groupingBy(...) Collectors.partitioningBy(...) Collectors.joining(",") Collectors.counting()
|
例子:按部门分组
1 2
| Map<Long, List<User>> map = users.stream() .collect(Collectors.groupingBy(User::getDeptId));
|
例子:转成 Map
1 2
| Map<Long, String> map = users.stream() .collect(Collectors.toMap(User::getId, User::getName));
|
坑点
- 链太长时可读性会下降
- 涉及副作用、复杂流程时,普通 for 有时更清楚
toMap 要注意 key 冲突
5. 方法引用 :::让 lambda 更干净
替代什么
替代那些“只是调用现成方法”的 lambda。
传统 lambda
1
| list.stream().map(user -> user.getName()).toList();
|
方法引用
1
| list.stream().map(User::getName).toList();
|
常见形式
1 2 3 4
| User::getId String::trim System.out::println HashMap::new
|
适用场景
坑点
如果方法引用反而让上下文更绕,就用普通 lambda。
6. switch 表达式:替代老式 switch
替代什么
替代 switch + break + 先定义变量后赋值 的老写法。
传统写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| String desc; switch (status) { case 1: desc = "待支付"; break; case 2: desc = "已支付"; break; case 3: desc = "已取消"; break; default: desc = "未知"; }
|
新写法
1 2 3 4 5 6
| String desc = switch (status) { case 1 -> "待支付"; case 2 -> "已支付"; case 3 -> "已取消"; default -> "未知"; };
|
多语句分支
1 2 3 4 5 6 7
| String result = switch (status) { case 1 -> { log.info("待支付状态"); yield "待支付"; } default -> "未知"; };
|
适用场景
7. Text Block:多行字符串不再丑
替代什么
替代 "...\n" + "...\n" + ... 这种多行字符串拼接。
传统写法
1 2 3
| String sql = "select * \n" + "from tb_user \n" + "where id = ?";
|
Text Block 写法
1 2 3 4 5
| String sql = """ select * from tb_user where id = ? """;
|
适用场景
- SQL
- JSON mock
- HTML 片段
- prompt 文本
- 长说明字符串
8. Lombok:自动生成常见样板
替代什么
替代 getter / setter / 构造器 / builder / 日志对象 等常见样板。
常用注解
1 2 3 4 5 6 7 8
| @Getter @Setter @ToString @EqualsAndHashCode @NoArgsConstructor @AllArgsConstructor @Builder @Slf4j
|
例子
1 2 3 4 5 6 7 8 9
| @Data @Builder @NoArgsConstructor @AllArgsConstructor public class UserDTO { private Long id; private String name; private Integer age; }
|
Builder 用法
1 2 3 4 5
| UserDTO user = UserDTO.builder() .id(1L) .name("Gilbert") .age(20) .build();
|
适用场景
- entity
- command / dto
- 配置对象
- 参数多的对象创建
坑点
@Data 不要无脑乱贴到所有类
- 对实体类要注意
equals/hashCode 范围
- 团队需要统一风格,否则会出现“有人爱 Lombok,有人不让用”的冲突
经验
- 简单数据载体:
record
- 可变对象 / entity:Lombok 往往更顺手
9. Bean Validation:参数校验声明式化
替代什么
替代 Controller / Service 里一堆手写 if 参数校验。
传统写法
1 2 3 4 5 6
| if (request.getUsername() == null || request.getUsername().isBlank()) { throw new IllegalArgumentException("用户名不能为空"); } if (request.getAge() == null || request.getAge() < 0) { throw new IllegalArgumentException("年龄不合法"); }
|
Bean Validation 写法
1 2 3 4 5 6
| public record RegisterRequest( @NotBlank String username, @NotNull @Min(0) Integer age, @Email String email ) { }
|
1 2 3 4
| @PostMapping("/register") public String register(@Valid @RequestBody RegisterRequest request) { return "ok"; }
|
常见注解
1 2 3 4 5 6 7 8
| @NotNull @NotBlank @NotEmpty @Min @Max @Size @Email @Pattern
|
适用场景
- Controller 入参
- 表单校验
- 接口参数约束
坑点
- 记得配合
@Valid
- 复杂跨字段校验可能要自定义注解
- 最好统一异常处理,别让错误响应乱飞
10. MapStruct:对象转换自动化
替代什么
替代“一个字段一个字段搬运”的低价值代码。
传统写法
1 2 3 4
| UserVO vo = new UserVO(); vo.setId(user.getId()); vo.setName(user.getName()); vo.setAge(user.getAge());
|
MapStruct 写法
1 2 3 4
| @Mapper(componentModel = "spring") public interface UserMapper { UserVO toVO(User user); }
|
使用
1
| UserVO vo = userMapper.toVO(user);
|
适用场景
- Entity -> VO
- Request -> Command
- DO -> DTO
- Page 数据转换
高收益点
在企业项目里,对象转换代码 often 非常多。MapStruct 能明显降低重复劳动。
坑点
- 需要编译期生成代码,IDE / Maven 配置要对
- 特殊字段映射要额外写配置
11. MyBatis-Plus:减少简单 CRUD / 条件查询代码
替代什么
替代简单 CRUD XML 和大量重复查询样板。
例子:链式查询
1 2 3 4
| lambdaQuery() .eq(User::getStatus, 1) .orderByDesc(User::getCreateTime) .list();
|
例子:字符串字段版
1 2 3
| query().eq("status", 1) .orderByDesc("create_time") .list();
|
推荐优先级
优先使用 lambda 风格:
1 2 3 4
| lambdaQuery() lambdaUpdate() Wrappers.lambdaQuery() Wrappers.lambdaUpdate()
|
适用场景
- 单表 CRUD
- 简单分页
- 常规条件查询
- 快速开发 demo / 中后台业务
坑点
- 复杂 SQL 仍然建议回到 MyBatis XML / 注解 SQL
- 不要为了链式而牺牲可读性
- 字符串字段名容易写错,能 lambda 就 lambda
12. 枚举承载行为:替代散落的 if/switch
替代什么
替代“状态码 everywhere + 到处 if/switch 做映射”。
普通枚举
1 2 3 4 5
| public enum OrderStatus { UNPAID, PAID, CANCELED }
|
更实用的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public enum OrderStatus { UNPAID(1, "待支付"), PAID(2, "已支付"), CANCELED(3, "已取消");
private final int code; private final String desc;
OrderStatus(int code, String desc) { this.code = code; this.desc = desc; }
public int getCode() { return code; }
public String getDesc() { return desc; } }
|
使用
1
| String desc = OrderStatus.PAID.getDesc();
|
进一步:枚举里放行为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public enum PayType { ALIPAY { @Override public void pay() { System.out.println("支付宝支付"); } }, WECHAT { @Override public void pay() { System.out.println("微信支付"); } };
public abstract void pay(); }
|
适用场景
13. Builder:解决长构造器 / 多可选参数
替代什么
替代“字段多时难读又容易错位的构造器调用”。
难读写法
1
| User user = new User(1L, "Gilbert", 20, "male", "Guangzhou", null, null);
|
Builder 写法
1 2 3 4 5 6
| User user = User.builder() .id(1L) .name("Gilbert") .age(20) .city("Guangzhou") .build();
|
适用场景
常见来源
- Lombok
@Builder
- 手写 Builder
14. 泛型统一返回体:减少重复响应结构
替代什么
替代每个接口都手搓一套相似响应结构。
例子
1 2 3 4 5 6 7 8 9 10
| public record Result<T>(boolean success, String message, T data) {
public static <T> Result<T> ok(T data) { return new Result<>(true, "ok", data); }
public static <T> Result<T> fail(String message) { return new Result<>(false, message, null); } }
|
使用
1 2
| return Result.ok(userVO); return Result.fail("用户不存在");
|
收益
- 统一响应格式
- 静态工厂简化调用
- 泛型避免重复定义
UserResult / OrderResult
15. 策略模式:把“分支型业务”从 if-else 中解放出来
替代什么
替代那种“分支越来越多”的支付、登录、优惠、消息发送逻辑。
传统 if-else
1 2 3 4 5 6 7
| if (type.equals("ALI")) { } else if (type.equals("WX")) { } else if (type.equals("CARD")) { }
|
策略接口
1 2 3
| public interface PayStrategy { void pay(Order order); }
|
具体实现
1 2 3 4 5 6
| public class AliPayStrategy implements PayStrategy { @Override public void pay(Order order) { System.out.println("支付宝支付: " + order.getId()); } }
|
1 2 3 4 5 6
| public class WechatPayStrategy implements PayStrategy { @Override public void pay(Order order) { System.out.println("微信支付: " + order.getId()); } }
|
上下文调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class PayService { private final Map<String, PayStrategy> strategyMap;
public PayService(Map<String, PayStrategy> strategyMap) { this.strategyMap = strategyMap; }
public void pay(String type, Order order) { PayStrategy strategy = strategyMap.get(type); if (strategy == null) { throw new IllegalArgumentException("不支持的支付方式"); } strategy.pay(order); } }
|
适用场景
- 多支付渠道
- 多登录方式
- 多消息发送方式
- 多优惠计算规则
坑点
- 分支很少时,别过度设计
- 不是所有 if-else 都要上策略模式
16. 建议学习顺序
第一阶段:立刻能上手
record
var
Optional
Stream
- 方法引用
::
switch 表达式
- Text Block
第二阶段:项目提效最明显
- Lombok
- Bean Validation
- MapStruct
- MyBatis-Plus 链式查询
- 统一返回体 + 泛型
第三阶段:更有设计感
- Builder
- 枚举承载行为
- 策略模式
17. 高频组合拳
组合 1:Controller 入参
1 2 3 4 5 6
| public record RegisterRequest( @NotBlank String username, @NotBlank String password, @Email String email ) { }
|
组合 2:查数据 + 转 VO + 兜底
1 2 3
| UserVO vo = Optional.ofNullable(userService.getById(id)) .map(userMapper::toVO) .orElseThrow(() -> new RuntimeException("用户不存在"));
|
组合 3:集合筛选与转换
1 2 3 4
| List<Long> ids = users.stream() .filter(User::isEnabled) .map(User::getId) .toList();
|
组合 4:状态映射
1 2 3 4 5
| String desc = switch (status) { case 1 -> "正常"; case 0 -> "禁用"; default -> "未知"; };
|
组合 5:快速单表业务
1 2 3 4 5
| List<User> list = lambdaQuery() .eq(User::getStatus, 1) .like(User::getName, keyword) .orderByDesc(User::getCreateTime) .list();
|
最后一句总结
Java 里这些“高阶用法”的共同目标,不是为了让代码看起来 fancy,而是为了做到:
把样板代码最小化,把业务表达最大化。
你以后看到一段代码时,可以先问自己三句:
- 这段是不是在重复搬运数据?
- 这段是不是在手写集合处理流程?
- 这段是不是在做声明式工具已经能做的事情?
如果答案是 yes,大概率就有更 modern 的写法可以替代。
一页速记表
| 目标 |
推荐武器 |
| 干掉 DTO 样板 |
record |
| 降低局部变量噪音 |
var |
| 干掉 null if-else |
Optional |
| 干掉手写遍历筛选 |
Stream API |
| 干掉短 lambda 噪音 |
方法引用 :: |
| 干掉老式 switch |
switch 表达式 |
| 干掉多行字符串拼接 |
Text Block |
| 干掉 getter / setter / builder |
Lombok |
| 干掉手写参数校验 |
Bean Validation |
| 干掉对象搬运 |
MapStruct |
| 干掉简单 CRUD 样板 |
MyBatis-Plus |
| 干掉状态分支散落 |
枚举承载行为 |
| 干掉长构造器 |
Builder |
| 干掉重复响应体 |
泛型统一返回体 |
| 干掉分支型业务 if-else |
策略模式 |