第 2 课 Java 21 新特性速览
本课讲解 Java 21(项目要求的 JDK 版本)的新特性。重点不是「背完所有新特性」,而是:项目真实用到了哪些、你该知道哪些。学完你会对「这个项目为什么要求 Java 21」有清晰的答案。
本课学习目标
完成本课后,你将能够:
- 说清 Java 21 是什么、为什么项目要求它
- 指出项目真正用到的 3 个 Java 21 新特性,并看懂对应代码
- 认识 record、sealed、虚拟线程等「可用但本项目没用到」的特性
- 建立「以源码为准」的认知:文档说用了 ≠ 代码真的用了
一、知识点详解
1.1 Java 21 是什么
Java 21 是 2023 年 9 月发布的 LTS(长期支持)版本,是继 Java 17 之后的第二个主流 LTS,也是很多新项目默认选择的版本。它本身没有「革命性」语法大改,而是在 Java 8-17 累积的众多新特性基础上做了最终定型。
本项目在根 pom.xml 里声明:
<properties>
<java.version>21</java.version>
</properties>
也就是说:这个项目必须用 JDK 21 编译运行,低于 21 无法构建。这是「为什么第 1 课要求装 JDK 21」的原因。
需要先说明一个真相:novel-plus 的代码风格其实偏「保守」——绝大多数代码还是传统 Java 8 风格(
Map、getter/setter、new ArrayList等)。它只零星用了少数新特性。所以本课分两类讲:项目真正用的 和 你需要知道但项目没用到的。
1.2 项目真正用到的 3 个新特性(重点)
① 文本块(Text Blocks)
是什么:用 """ 包裹多行字符串,不用再写一堆 n 和转义,缩进也会自动对齐。Java 15 起正式可用。
项目里的真实例子 —— novel-front/.../service/impl/LikeServiceImpl.java(第 35 行附近):
@PostConstruct
public void init() {
// Lua 脚本保证原子性操作
String script = """
local key = KEYS[1]
local userId = ARGV[1]
local isLiked = redis.call('SISMEMBER', key, userId)
if isLiked == 1 then
redis.call('SREM', key, userId)
else
redis.call('SADD', key, userId)
end
return redis.call('SCARD', key)
""";
...
}
为什么这里用文本块:这段 Lua 脚本天然是多行、有缩进、含单引号的代码,用传统字符串写要手拼换行、极易出错;文本块让它「所见即所得」。这也是点赞功能的原子操作(点赞/取消赞共用一段 Redis Lua 脚本)。
② instanceof 模式匹配(Pattern Matching for instanceof)
是什么:instanceof 判断类型后直接声明变量,省掉后续手动强制转换。Java 16 起正式可用。
项目里的真实例子 —— novel-admin/.../common/aspect/MapSortValidationAspect.java(第 56-60 行):
if (hasAnnotation && args[i] instanceof Map map) { // 判断 + 绑定变量 map
if (map.get("sort") instanceof String sortStr) { // 判断 + 绑定变量 sortStr
map.put("sort", SortWhitelistUtil.sanitizeColumn(sortStr));
}
if (map.get("order") instanceof String orderStr) {
map.put("order", SortWhitelistUtil.sanitizeOrder(orderStr));
}
}
和旧写法的区别:
// 旧写法:判断 + 手动强转
if (args[i] instanceof Map) {
Map map = (Map) args[i];
...
}
// 新写法:一步到位
if (args[i] instanceof Map map) { ... }
这里还顺带体现了 Lambda + Stream(Arrays.stream(parameterAnnotations[i]).anyMatch(...)),后面 1.3 会再提。
③ 增强 switch 表达式(Switch Expressions)
是什么:switch 既可以当「语句」也可以当「表达式」(有返回值),用 -> 箭头标签,不需要 break。Java 14 起正式可用。
项目里的真实例子 —— novel-crawl/.../core/schedule/DiskMonitorSchedule.java(第 91-96 行):
private String getAlertLevelText(String level) {
return switch (level) {
case "CRITICAL" -> "严重";
case "WARNING" -> "警告";
case "INFO" -> "提示";
default -> "通知";
};
}
和旧写法的区别:
// 旧写法:语句 + break,容易漏 break
String text;
switch (level) {
case "CRITICAL": text = "严重"; break;
case "WARNING": text = "警告"; break;
default: text = "通知";
}
// 新写法:表达式直接返回值
return switch (level) {
case "CRITICAL" -> "严重";
default -> "通知";
};
DiskMonitorSchedule 是一个磁盘监控定时任务(爬虫模块专用),同一个 switch (level) 套路在文件里写了 5 处,分别把等级转成中文、图标、颜色、邮件正文等——是观察 switch 表达式的最佳样本。
1.3 项目大量使用的「现代 Java 特性」(非 21 专属,快速带过)
这些不是 Java 21 才有的,但项目到处在用,值得在学源码前心里有数:
| 特性 | 项目位置 | 一句话说明 |
|---|---|---|
| Lambda + Stream | MapSortValidationAspect、PageController |
函数式处理集合,替代 for 循环 |
| Optional | novel-crawl/.../core/crawl/CrawlParser.java |
优雅处理「可能为空」,链式调用 |
| CompletableFuture | novel-front/.../controller/page/PageController.java |
异步并行加载首页数据,提升性能 |
| try-with-resources | 各模块(如数据库连接、流) | 资源自动关闭,无需 finally |
这些在后续课程(如第 13 课请求链路、第 26 课异步)会展开,本课先认识它们存在。
1.4 大纲提到、但项目其实没用到的新特性(了解即可)
本课程大纲和 doc/tech-stack-learning-roadmap.md 2.1 节都提到「Record、Sealed Class 等」——但我核实过全部源码,项目里并没有声明任何 record / sealed 类,也没有虚拟线程和 var。这是很好的实战提醒:读文档要有怀疑精神,以源码为准。
这几个特性你仍然应该知道(面试和写新代码会用):
-
record(记录类):一句话定义「不可变数据类」,自动生成构造器、getter、equals/hashCode/toString:
record BookInfo(String bookName, int wordCount) { } // 自动获得 new BookInfo("斗破", 3000) 和 bookInfo.bookName()项目没用(实体类都是传统 POJO + Lombok 的
@Data),但新代码里它是「DTO/VO」的最佳替代。 -
sealed(密封类/接口):限制一个类的子类必须由自己声明:
sealed interface CrawlSource permits AibiSource, QidianSource { }项目未用(爬虫源之间没有这种强约束)。
-
虚拟线程(Virtual Threads):轻量级线程,可创建成千上万个处理 IO,Java 21 的杀手锏。项目未用(仍是传统线程池,见第 26 课
ThreadPoolConfig)。 -
var(局部变量类型推断):Java 10 就有,
var list = new ArrayList<String>();项目未用(风格保守)。 -
switch 模式匹配:
case String s -> ...直接在 switch 里解构类型,项目未用。
1.5 小结:为什么「要求 Java 21」却「用得少」
- 要求 Java 21:现代 LTS,安全、性能好、生态默认版本;项目方从 Java 8 升级上来的。
- 用得少:老代码风格迁移成本高,只在顺手处用新特性。
- 对你的意义:二次开发时可以放心用 record / switch 表达式 / 文本块(JDK 21 都支持),也能读懂项目里那 3 处新特性代码。知道是什么 + 能在源码里认出来,就够本课目标了。
二、动手练习(含验证标准)
做完一项勾一项。
练习 1:定位源码中的新特性
- 目标:在 IDE 里找到本课讲到的 3 处新特性代码,逐一读懂。
- 步骤:用 Ctrl+Shift+N 打开
LikeServiceImpl、MapSortValidationAspect、DiskMonitorSchedule,跳到对应行(35 / 56-60 / 91-96),对照本课讲解看一遍。 - 验证标准:能不看本文说出——三处分别用了什么特性、分别解决了什么问题(Lua 多行脚本 / 免强转 / switch 返回值)。
练习 2:上手新特性(写 5 分钟小代码)
- 目标:亲手写 record 和文本块,确认编译运行。
-
步骤:任意建一个 Java 类(可用 IDEA 的临时 Scratch File):
record BookInfo(String bookName, int wordCount) { } public class Test { public static void main(String[] args) { var b = new BookInfo("斗破苍穹", 3000); // record + var String tip = """ 书名:%s 字数:%d """.formatted(b.bookName(), b.wordCount()); // 文本块 System.out.println(tip); } } - 验证标准:编译通过,输出两行内容;说明 record 自动生成了
bookName()等访问方法。
练习 3:把传统 switch 改写成 switch 表达式
- 目标:学会新写法,且保证行为不变。
- 步骤:打开
novel-front/.../controller/CacheController.java(第 48 行switch (type))或BookServiceImpl.java(第 324 行switch (type)),在草稿里把传统case x: ... break;改写为->箭头形式。 - 验证标准:改写后逻辑等价(对照原 switch 每个分支的值),能讲出
->分支为什么不需要break。 - 注意:练习是「在草稿里改着玩」,不必真的改项目文件。
三、本课小结
novel-plus 要求 JDK 21,但代码风格偏保守:真正用到的新特性只有 3 处——文本块(LikeServiceImpl 的 Lua 脚本)、instanceof 模式匹配(MapSortValidationAspect 免强转)、增强 switch 表达式(DiskMonitorSchedule 的磁盘告警文案)。其余现代特性(Lambda/Stream/Optional/CompletableFuture)项目大量使用但非 21 专属;record、sealed、虚拟线程、var 等「可用未用」。判断一个技术用没用,以源码为准,别全信文档。
四、自测题
- 【选择】项目里文本块用在哪里?(A.
DiskMonitorScheduleB.LikeServiceImplC.MapSortValidationAspectD.CrawlParser) - 【选择】增强 switch 表达式用哪个符号作为分支标签?(A.
:B.::C.->D.=>) - 【选择】record 的主要用途是?(A. 多线程并发 B. 一句话定义不可变数据类 C. 字符串模板 D. 类型推断)
- 【简答】为什么说「文档声称项目用了 Record/Sealed,但实际没有」?学习时应该以什么为准?
- 【简答】
instanceof Map map比instanceof Map省掉了哪一步?
答案:1-B(见 1.2①)、2-C(见 1.2③)、3-B(见 1.4)、4-源码为准(见 1.4 引言)、5-手动强转(见 1.2②)。
五、参考资源
- 本课程大纲:
Tutorial/课程大纲-30节课.md→ 第 2 课 - 技术栈路线:
doc/tech-stack-learning-roadmap.md→ 2.1 节 Java 21(注意其 Record/Sealed 说法与源码不符) - 官方文档:Oracle Java 21 语言特性(docs.oracle.com/en/java/javase/21)
- 书目:《Java 核心技术 卷 I》第 12 版(覆盖 Java 17-21 新特性)