第 2 课 Java 21 新特性速览
本文最后更新于11 天前,其中的信息可能已经过时,如有错误请发送邮件到kirasu@qq.com

第 2 课 Java 21 新特性速览

本课讲解 Java 21(项目要求的 JDK 版本)的新特性。重点不是「背完所有新特性」,而是:项目真实用到了哪些、你该知道哪些。学完你会对「这个项目为什么要求 Java 21」有清晰的答案。


本课学习目标

完成本课后,你将能够:

  1. 说清 Java 21 是什么、为什么项目要求它
  2. 指出项目真正用到的 3 个 Java 21 新特性,并看懂对应代码
  3. 认识 record、sealed、虚拟线程等「可用但本项目没用到」的特性
  4. 建立「以源码为准」的认知:文档说用了 ≠ 代码真的用了

一、知识点详解

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 等「可用未用」。判断一个技术用没用,以源码为准,别全信文档。


四、自测题

  1. 【选择】项目里文本块用在哪里?(A. DiskMonitorSchedule  B. LikeServiceImpl C. MapSortValidationAspect D. CrawlParser)
  2. 【选择】增强 switch 表达式用哪个符号作为分支标签?(A. : B. :: C. -> D. =>)
  3. 【选择】record 的主要用途是?(A. 多线程并发 B. 一句话定义不可变数据类 C. 字符串模板 D. 类型推断)
  4. 【简答】为什么说「文档声称项目用了 Record/Sealed,但实际没有」?学习时应该以什么为准?
  5. 【简答】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 新特性)
文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇