前言
"很多人升级 JDK 后,第一反应不是性能变好了,而是代码终于没那么啰嗦了。"
Java 14 到 Java 16 这一段演进,最直接的价值就是持续减少样板代码。
这篇文章聚焦 3 个非常有代表性的能力:instanceof 模式匹配、文本块、Record。它们不一定改变你的系统架构,但会持续改善你每天写代码的手感。

一、instanceof 模式匹配:把判断和转换合成一步
先说一个容易写错的版本点:
- JDK 14:预览特性
- JDK 15:第二次预览
- JDK 16:正式定稿
所以更准确的表述不是“Java 14 引入后就能稳定使用”,而是“从 JDK 14 开始预览,到 JDK 16 正式可用”。
传统写法:
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str.length());
}
模式匹配写法:
if (obj instanceof String str) {
System.out.println(str.length());
}
实战里非常适合这种场景:
if (request instanceof ContentCachingRequestWrapper wrapper) {
byte[] content = wrapper.getContentAsByteArray();
}
Object score = response.get("score");
if (score instanceof Number number) {
return number.doubleValue();
}
二、文本块:多行字符串终于能好好写了
文本块在 Java 15 正式定稿,用三个双引号 """ 表示多行字符串。
传统写法:
String json = "{\n" +
" \"name\": \"张三\",\n" +
" \"age\": 25\n" +
"}";
文本块写法:
String json = """
{
"name": "张三",
"age": 25
}
""";
最适合哪些场景
- SQL
- JSON
- HTML 模板
- 测试请求体
例如 MyBatis 注解写 SQL:
@Select("""
SELECT id, username, email, status
FROM sys_user
WHERE status = #{status}
ORDER BY created_at DESC
""")
List<SysUserEntity> selectByStatus(@Param("status") String status);
动态 SQL 也能配合 <script>:
@Select("""
<script>
SELECT * FROM sys_user
WHERE deleted = false
<if test="status != null">
AND status = #{status}
</if>
<if test="keyword != null and keyword != ''">
AND (username LIKE CONCAT('%', #{keyword}, '%')
OR email LIKE CONCAT('%', #{keyword}, '%'))
</if>
</script>
""")
List<SysUserEntity> selectByCondition(@Param("status") String status,
@Param("keyword") String keyword);
测试代码里也很舒服:
mockMvc.perform(post("/api/admin/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"loginId": "admin",
"password": "Admin@123456"
}
"""));
📌 注意点
- 文本块不支持变量插值,但可以配合
formatted() - 缩进规则要理解清楚,必要时用
stripIndent() - 行尾的
\可以消除换行
三、Record:DTO 样板代码的终结者
Record 在 Java 16 正式定稿,用来定义“不可变数据载体”非常合适。
传统 DTO:
public class UserDTO {
private final Long id;
private final String username;
private final String email;
public UserDTO(Long id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
}
public Long getId() { return id; }
public String getUsername() { return username; }
public String getEmail() { return email; }
}
Record 写法:
public record UserDTO(Long id, String username, String email) {}
很适合的场景
public record ModelResponse(
Long id,
Long providerId,
String providerCode,
String providerName,
String modelCode,
String modelName,
List<String> capabilityTypes,
String modelType,
Integer maxTokens,
BigDecimal temperatureDefault,
String status
) {}
还可以加注解、方法、紧凑构造器:
private record BailianChatUsage(
@JsonProperty("prompt_tokens") Integer promptTokens,
@JsonProperty("completion_tokens") Integer completionTokens
) {}
public record RetrievedChunk(ChunkEntity chunk, double score) {
public RetrievedChunk {
if (score < 0 || score > 1) {
throw new IllegalArgumentException("score must be between 0 and 1");
}
}
}
Record 和 Lombok 怎么选
| 场景 | 推荐 |
|---|---|
| API 响应 DTO | Record |
| 内部只读数据载体 | Record |
| JPA 实体类 | Lombok / 普通类 |
| 需要可变对象 | Lombok @Data 或普通类 |
| 需要继承 | 普通类或 Lombok |
📌 注意点
- Record 适合“数据载体”,不适合承担复杂可变业务行为
- Record 不能继承普通类,但可以实现接口
- Record 天生更适合不可变建模
总结
Java 14 到 16 这段演进带来的最直观收益,就是“少写废话”:
instanceof模式匹配减少强转样板代码- 文本块让多行字符串终于可读
- Record 让 DTO 回归数据本身
如果你已经在 Java 17+ 或 Java 21 上开发,这些能力完全应该成为默认写法的一部分。
欢迎关注公众号 FishTech Notes,一块交流使用心得!