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