减少Java样板代码

Java 去样板代码 Cheatsheet

面向 Java 后端开发的 modern Java / engineering tricks。目标不是炫技,而是把那些“必须写、但信息密度很低”的样板代码压掉,把注意力留给业务本身。


目录

  1. record:干掉 DTO / VO / Request 样板
  2. var:减少局部变量类型噪音
  3. Optional:减少 null if-else
  4. Stream API:声明式集合处理
  5. 方法引用 :::让 lambda 更干净
  6. switch 表达式:替代老式 switch
  7. Text Block:多行字符串不再丑
  8. Lombok:自动生成常见样板
  9. Bean Validation:参数校验声明式化
  10. MapStruct:对象转换自动化
  11. MyBatis-Plus:减少简单 CRUD / 条件查询代码
  12. 枚举承载行为:替代散落的 if/switch
  13. Builder:解决长构造器 / 多可选参数
  14. 泛型统一返回体:减少重复响应结构
  15. 策略模式:把“分支型业务”从 if-else 中解放出来
  16. 建议学习顺序
  17. 高频组合拳

1. record:干掉 DTO / VO / Request 样板

替代什么

替代那种只负责装数据的 POJO:字段、构造器、getter、equals/hashCode/toString

传统写法

代码块JAVA · 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 写法

代码块JAVA · 2 行收起展开
public record LoginRequest(String username, String password) {
}

使用方式

代码块JAVA · 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:减少局部变量类型噪音

替代什么

替代那些右侧类型已经非常明显、但左侧又写一遍很啰嗦的局部变量声明。

传统写法

代码块JAVA · 2 行收起展开
Map<Long, List<User>> userMap = users.stream()
        .collect(Collectors.groupingBy(User::getDeptId));

var 写法

代码块JAVA · 2 行收起展开
var userMap = users.stream()
        .collect(Collectors.groupingBy(User::getDeptId));

适用场景

  • 泛型很长
  • 右边类型很明显
  • 局部临时变量

不推荐场景

代码块JAVA · 1 行收起展开
var x = getSomething();

右边看不出类型时,会伤害可读性。

经验

var 是为了让视觉重心落到逻辑,而不是为了显得高级。


3. Optional:减少 null if-else

替代什么

替代多层 null 判断和默认值处理。

传统写法

代码块JAVA · 8 行收起展开
User user = userService.getById(id);
if (user != null) {
    String name = user.getName();
    if (name != null) {
        return name.trim();
    }
}
return "unknown";

Optional 写法

代码块JAVA · 4 行收起展开
return Optional.ofNullable(userService.getById(id))
        .map(User::getName)
        .map(String::trim)
        .orElse("unknown");

高频写法

代码块JAVA · 4 行收起展开
Optional.ofNullable(obj)
        .map(...)
        .filter(...)
        .orElse(defaultValue);

适用场景

  • 可能为空的返回值
  • 多层取值
  • 默认值 fallback
  • 查不到则抛异常

例子:查不到抛异常

代码块JAVA · 2 行收起展开
User user = Optional.ofNullable(userService.getById(id))
        .orElseThrow(() -> new RuntimeException("用户不存在"));

坑点

  • 不要把 Optional 当成实体类 / DTO 字段类型到处用
  • 不要写成复杂 monad 表演赛,超过可读性就收手

4. Stream API:声明式集合处理

替代什么

替代“遍历 + 判断 + 转换 + 收集”的大量 for 循环。

传统写法

代码块JAVA · 6 行收起展开
List<Long> ids = new ArrayList<>();
for (User user : users) {
    if (user.getAge() >= 18) {
        ids.add(user.getId());
    }
}

Stream 写法

代码块JAVA · 4 行收起展开
List<Long> ids = users.stream()
				      .filter(user -> user.getAge() >= 18)
				      .map(User::getId)
				      .toList();

高频模板

代码块JAVA · 6 行收起展开
list.stream()
    .filter(...)
    .map(...)
    .distinct()
    .sorted()
    .toList();

高频收集器

代码块JAVA · 7 行收起展开
Collectors.toList()
Collectors.toSet()
Collectors.toMap(...)
Collectors.groupingBy(...)
Collectors.partitioningBy(...)
Collectors.joining(",")
Collectors.counting()

例子:按部门分组

代码块JAVA · 2 行收起展开
Map<Long, List<User>> map = users.stream()
        .collect(Collectors.groupingBy(User::getDeptId));

例子:转成 Map

代码块JAVA · 2 行收起展开
Map<Long, String> map = users.stream()
        .collect(Collectors.toMap(User::getId, User::getName));

坑点

  • 链太长时可读性会下降
  • 涉及副作用、复杂流程时,普通 for 有时更清楚
  • toMap 要注意 key 冲突

5. 方法引用 :::让 lambda 更干净

替代什么

替代那些“只是调用现成方法”的 lambda。

传统 lambda

代码块JAVA · 1 行收起展开
list.stream().map(user -> user.getName()).toList();

方法引用

代码块JAVA · 1 行收起展开
list.stream().map(User::getName).toList();

常见形式

代码块JAVA · 4 行收起展开
User::getId
String::trim
System.out::println
HashMap::new

适用场景

  • map
  • filter
  • forEach
  • 构造对象

坑点

如果方法引用反而让上下文更绕,就用普通 lambda。


6. switch 表达式:替代老式 switch

替代什么

替代 switch + break + 先定义变量后赋值 的老写法。

传统写法

代码块JAVA · 14 行收起展开
String desc;
switch (status) {
    case 1:
        desc = "待支付";
        break;
    case 2:
        desc = "已支付";
        break;
    case 3:
        desc = "已取消";
        break;
    default:
        desc = "未知";
}

新写法

代码块JAVA · 6 行收起展开
String desc = switch (status) {
    case 1 -> "待支付";
    case 2 -> "已支付";
    case 3 -> "已取消";
    default -> "未知";
};

多语句分支

代码块JAVA · 7 行收起展开
String result = switch (status) {
    case 1 -> {
        log.info("待支付状态");
        yield "待支付";
    }
    default -> "未知";
};

适用场景

  • 状态码映射
  • 类型选择
  • 策略分发的轻量版

7. Text Block:多行字符串不再丑

替代什么

替代 "...\n" + "...\n" + ... 这种多行字符串拼接。

传统写法

代码块JAVA · 3 行收起展开
String sql = "select * \n" +
             "from tb_user \n" +
             "where id = ?";

Text Block 写法

代码块JAVA · 5 行收起展开
String sql = """
        select *
        from tb_user
        where id = ?
        """;

适用场景

  • SQL
  • JSON mock
  • HTML 片段
  • prompt 文本
  • 长说明字符串

8. Lombok:自动生成常见样板

替代什么

替代 getter / setter / 构造器 / builder / 日志对象 等常见样板。

常用注解

代码块JAVA · 8 行收起展开
@Getter
@Setter
@ToString
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Slf4j

例子

代码块JAVA · 9 行收起展开
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
    private Long id;
    private String name;
    private Integer age;
}

Builder 用法

代码块JAVA · 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 参数校验。

传统写法

代码块JAVA · 6 行收起展开
if (request.getUsername() == null || request.getUsername().isBlank()) {
    throw new IllegalArgumentException("用户名不能为空");
}
if (request.getAge() == null || request.getAge() < 0) {
    throw new IllegalArgumentException("年龄不合法");
}

Bean Validation 写法

代码块JAVA · 6 行收起展开
public record RegisterRequest(
        @NotBlank String username,
        @NotNull @Min(0) Integer age,
        @Email String email
) {
}
代码块JAVA · 4 行收起展开
@PostMapping("/register")
public String register(@Valid @RequestBody RegisterRequest request) {
    return "ok";
}

常见注解

代码块JAVA · 8 行收起展开
@NotNull
@NotBlank
@NotEmpty
@Min
@Max
@Size
@Email
@Pattern

适用场景

  • Controller 入参
  • 表单校验
  • 接口参数约束

坑点

  • 记得配合 @Valid
  • 复杂跨字段校验可能要自定义注解
  • 最好统一异常处理,别让错误响应乱飞

10. MapStruct:对象转换自动化

替代什么

替代“一个字段一个字段搬运”的低价值代码。

传统写法

代码块JAVA · 4 行收起展开
UserVO vo = new UserVO();
vo.setId(user.getId());
vo.setName(user.getName());
vo.setAge(user.getAge());

MapStruct 写法

代码块JAVA · 4 行收起展开
@Mapper(componentModel = "spring")
public interface UserMapper {
    UserVO toVO(User user);
}

使用

代码块JAVA · 1 行收起展开
UserVO vo = userMapper.toVO(user);

适用场景

  • Entity -> VO
  • Request -> Command
  • DO -> DTO
  • Page 数据转换

高收益点

在企业项目里,对象转换代码 often 非常多。MapStruct 能明显降低重复劳动。

坑点

  • 需要编译期生成代码,IDE / Maven 配置要对
  • 特殊字段映射要额外写配置

11. MyBatis-Plus:减少简单 CRUD / 条件查询代码

替代什么

替代简单 CRUD XML 和大量重复查询样板。

例子:链式查询

代码块JAVA · 4 行收起展开
lambdaQuery()
        .eq(User::getStatus, 1)
        .orderByDesc(User::getCreateTime)
        .list();

例子:字符串字段版

代码块JAVA · 3 行收起展开
    query().eq("status", 1)
	       .orderByDesc("create_time")
	       .list();

推荐优先级

优先使用 lambda 风格:

代码块JAVA · 4 行收起展开
lambdaQuery()
lambdaUpdate()
Wrappers.lambdaQuery()
Wrappers.lambdaUpdate()

适用场景

  • 单表 CRUD
  • 简单分页
  • 常规条件查询
  • 快速开发 demo / 中后台业务

坑点

  • 复杂 SQL 仍然建议回到 MyBatis XML / 注解 SQL
  • 不要为了链式而牺牲可读性
  • 字符串字段名容易写错,能 lambda 就 lambda

12. 枚举承载行为:替代散落的 if/switch

替代什么

替代“状态码 everywhere + 到处 if/switch 做映射”。

普通枚举

代码块JAVA · 5 行收起展开
public enum OrderStatus {
    UNPAID,
    PAID,
    CANCELED
}

更实用的写法

代码块JAVA · 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;
    }
}

使用

代码块JAVA · 1 行收起展开
String desc = OrderStatus.PAID.getDesc();

进一步:枚举里放行为

代码块JAVA · 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:解决长构造器 / 多可选参数

替代什么

替代“字段多时难读又容易错位的构造器调用”。

难读写法

代码块JAVA · 1 行收起展开
User user = new User(1L, "Gilbert", 20, "male", "Guangzhou", null, null);

Builder 写法

代码块JAVA · 6 行收起展开
User user = User.builder()
        .id(1L)
        .name("Gilbert")
        .age(20)
        .city("Guangzhou")
        .build();

适用场景

  • 字段很多
  • 可选参数很多
  • 对象创建复杂

常见来源

  • Lombok @Builder
  • 手写 Builder

14. 泛型统一返回体:减少重复响应结构

替代什么

替代每个接口都手搓一套相似响应结构。

例子

代码块JAVA · 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);
    }
}

使用

代码块JAVA · 2 行收起展开
return Result.ok(userVO);
return Result.fail("用户不存在");

收益

  • 统一响应格式
  • 静态工厂简化调用
  • 泛型避免重复定义 UserResult / OrderResult

15. 策略模式:把“分支型业务”从 if-else 中解放出来

替代什么

替代那种“分支越来越多”的支付、登录、优惠、消息发送逻辑。

传统 if-else

代码块JAVA · 7 行收起展开
if (type.equals("ALI")) {
    // 支付宝逻辑
} else if (type.equals("WX")) {
    // 微信逻辑
} else if (type.equals("CARD")) {
    // 银行卡逻辑
}

策略接口

代码块JAVA · 3 行收起展开
public interface PayStrategy {
    void pay(Order order);
}

具体实现

代码块JAVA · 6 行收起展开
public class AliPayStrategy implements PayStrategy {
    @Override
    public void pay(Order order) {
        System.out.println("支付宝支付: " + order.getId());
    }
}
代码块JAVA · 6 行收起展开
public class WechatPayStrategy implements PayStrategy {
    @Override
    public void pay(Order order) {
        System.out.println("微信支付: " + order.getId());
    }
}

上下文调用

代码块JAVA · 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. 建议学习顺序

第一阶段:立刻能上手

  1. record
  2. var
  3. Optional
  4. Stream
  5. 方法引用 ::
  6. switch 表达式
  7. Text Block

第二阶段:项目提效最明显

  1. Lombok
  2. Bean Validation
  3. MapStruct
  4. MyBatis-Plus 链式查询
  5. 统一返回体 + 泛型

第三阶段:更有设计感

  1. Builder
  2. 枚举承载行为
  3. 策略模式

17. 高频组合拳

组合 1:Controller 入参

代码块JAVA · 6 行收起展开
public record RegisterRequest(
        @NotBlank String username,
        @NotBlank String password,
        @Email String email
) {
}
  • record
  • Bean Validation

组合 2:查数据 + 转 VO + 兜底

代码块JAVA · 3 行收起展开
UserVO vo = Optional.ofNullable(userService.getById(id))
        .map(userMapper::toVO)
        .orElseThrow(() -> new RuntimeException("用户不存在"));
  • Optional
  • MapStruct

组合 3:集合筛选与转换

代码块JAVA · 4 行收起展开
List<Long> ids = users.stream()
        .filter(User::isEnabled)
        .map(User::getId)
        .toList();
  • Stream
  • 方法引用

组合 4:状态映射

代码块JAVA · 5 行收起展开
String desc = switch (status) {
    case 1 -> "正常";
    case 0 -> "禁用";
    default -> "未知";
};
  • switch 表达式

组合 5:快速单表业务

代码块JAVA · 5 行收起展开
List<User> list = lambdaQuery()
        .eq(User::getStatus, 1)
        .like(User::getName, keyword)
        .orderByDesc(User::getCreateTime)
        .list();
  • MyBatis-Plus lambda 链式查询

最后一句总结

Java 里这些“高阶用法”的共同目标,不是为了让代码看起来 fancy,而是为了做到:

把样板代码最小化,把业务表达最大化。

你以后看到一段代码时,可以先问自己三句:

  1. 这段是不是在重复搬运数据?
  2. 这段是不是在手写集合处理流程?
  3. 这段是不是在做声明式工具已经能做的事情?

如果答案是 yes,大概率就有更 modern 的写法可以替代。


一页速记表

目标推荐武器
干掉 DTO 样板record
降低局部变量噪音var
干掉 null if-elseOptional
干掉手写遍历筛选Stream API
干掉短 lambda 噪音方法引用 ::
干掉老式 switchswitch 表达式
干掉多行字符串拼接Text Block
干掉 getter / setter / builderLombok
干掉手写参数校验Bean Validation
干掉对象搬运MapStruct
干掉简单 CRUD 样板MyBatis-Plus
干掉状态分支散落枚举承载行为
干掉长构造器Builder
干掉重复响应体泛型统一返回体
干掉分支型业务 if-else策略模式