摘要本文深入解析 JDK 21 中四个提升开发效率的实用语法糖局部变量类型推断var、文本块格式化方法formatted()、简洁数据类Record以及未命名变量_。通过对比新旧写法与综合示例展示如何用更少的代码实现相同功能让 Java 代码更简洁、优雅、易读。关键词Java 语法糖, var 类型推断, Record 数据类, 文本块格式化, 未命名变量不知不觉我们已经写完了前十四篇文章。从变量到运算符从条件判断到循环从数组到字符串——你的 Java 知识库已经初具规模了。现在回头看你可能会觉得有些代码“有点啰嗦”。比如写一个ArrayListString的时候左边写一遍ArrayListString右边new的时候又写一遍——重复。再比如定义一个只有几个字段的数据类要写构造方法、getter、setter、toString……满满一屏的“样板代码”——繁琐。Java 语言的设计者也意识到了这些问题。从 Java 10 开始他们陆续引入了一系列“语法糖”——这些特性不改变程序运行的底层逻辑但能让代码写起来更短、读起来更舒服。今天我们就来认识 JDK 21 中几个最实用的语法糖var、文本块的formatted()方法、Record以及未命名变量。它们不会让你多学会什么新功能但会让你写代码的时候少打很多字。1.var—— 让编译器帮你“猜”类型在 Java 10 之前声明一个变量必须显式写出类型// Java 8 的写法Stringname张三;ListStringlistnewArrayListString();MapString,ListIntegermapnewHashMapString,ListInteger();有没有觉得右边写的那一串类型很冗余明明左边已经写了右边还得再写一遍。从 Java 10 开始引入了一个新关键字var它让编译器根据右边的初始化表达式自动推断变量的类型。// 使用 var 的写法varname张三;// 编译器推断为 StringvarlistnewArrayListString();// 推断为 ArrayListStringvarmapnewHashMapString,ListInteger();// 推断为 HashMapString, ListIntegervar不是动态类型不像 JavaScript 的var它只是编译时的类型推断——编译后var会被替换成具体的类型所以运行时性能和 Java 8 完全一样。var的使用规则var虽然方便但不是哪里都能用的。记住一个核心原则var只能用于局部变量方法内部、代码块内部并且必须有初始化值。场景是否可用说明方法内的局部变量✅ 可用var name 张三;代码块内的局部变量✅ 可用在if、for等代码块内循环变量✅ 可用for (var s : list) {}类的成员变量字段❌ 不可用成员变量需要明确的类型声明方法参数❌ 不可用方法签名需要明确类型方法返回值❌ 不可用返回值类型必须明确没有初始化值❌ 不可用var num;编译错误var的使用建议var虽然好但不要滥用。它的核心价值是减少重复的类型声明而不是把所有变量都改成var。推荐使用var的场景右侧初始化表达式已经清楚地表明了类型如new ArrayList()、List.of(...)循环遍历中的变量for (var item : items)简单的临时变量不推荐使用var的场景多态场景如var animal new Cat();会让类型变成Cat而非Animal多态失效复杂的泛型嵌套可读性会变差团队协作的核心公共代码为了可读性显式类型更清晰 一句话总结var让代码少打几个字但不改变任何运行逻辑。用它来减少冗余而不是隐藏信息。2. 文本块的formatted()方法 —— 多行模板的“填词游戏”我们在上一篇学习了文本块Text Blocks——用包裹的多行字符串。如果你还记得我们当时用formatted()方法往文本块里填充变量Stringname小明;intage20;Stringinfo 姓名%s 年龄%d .formatted(name,age);formatted()方法是在 JDK 15 中随着文本块一起引入的。它接受可变参数按照%s、%d、%f等占位符的顺序把参数填充进去。为什么用formatted()在没有formatted()之前如果要在多行字符串中插入变量只能用拼接既丑陋又容易出错// 传统方式用 拼接多行字符串Stringname张三;intage25;Stringjson{\n \name\: \name\,\n \age\: age\n};而用文本块 formatted()代码整洁得多// 现代方式文本块 formatted()Stringjson { name: %s, age: %d } .formatted(name,age);常见占位符占位符说明示例%s字符串姓名%s.formatted(张三)%d十进制整数年龄%d.formatted(25)%f浮点数价格%.2f.formatted(9.99)%n换行符跨平台换行formatted()尤其适合构建SQL 语句、JSON、HTML、配置文件等需要多行结构的文本。3.Record—— 告别繁琐的 POJO这是一个重量级的语法糖。在 Java 中我们经常需要定义一些“纯粹的数据类”——它们只有几个字段主要用来承载数据没什么复杂的业务逻辑。比如一个Person类有name和age两个字段。在 Java 16 之前你得这样写// Java 16 之前的写法至少 30 行代码publicclassPerson{privateStringname;privateintage;publicPerson(Stringname,intage){this.namename;this.ageage;}publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.namename;}publicintgetAge(){returnage;}publicvoidsetAge(intage){this.ageage;}OverridepublicStringtoString(){returnPerson{namename, ageage};}Overridepublicbooleanequals(Objecto){...}OverridepublicinthashCode(){...}}光是构造方法、getter/setter、toString、equals、hashCode这些“样板代码”就要写几十行。而且每个数据类都要重复一遍。Record就是为了解决这个问题而生的。从 Java 16 开始正式稳定JEP 395它让你用一行代码就能定义一个纯数据类。// 使用 Record一行搞定publicrecordPerson(Stringname,intage){}就这么简单。编译器会自动帮你生成构造方法Person(String name, int age)访问方法name()、age()——注意不叫getName()而是直接叫name()toString()方法equals()和hashCode()方法使用起来和普通类一样varpersonnewPerson(张三,25);System.out.println(person.name());// 输出张三System.out.println(person.age());// 输出25System.out.println(person);// 输出Person[name张三, age25]Record 的特点不可变Record 的所有字段都是final的一旦创建就不能修改。这让它天然适合作为数据传输对象DTO或值对象。简洁极大的减少了样板代码。透明Record 的字段和访问方法名称完全一致一目了然。Record 与普通类的选择场景推荐纯粹的数据载体不需要复杂逻辑✅ 用 Record需要可变状态字段可以修改用普通类需要继承其他类用普通类Record 不能继承其他类需要添加额外的业务方法Record 可以添加但推荐保持简洁 如果你在写 Spring Boot 项目Record 用来做 DTO数据传输对象简直是绝配。我们会在第三阶段面向对象篇深入学习 Record 的高级用法包括记录模式Record Patterns——它可以配合模式匹配解构 Record 中的字段。现在你只需要知道Record 让你用最少的代码定义数据类。4. 未命名变量 —— 用_表示“我不需要这个名字”这是 JDK 21 引入的一个预览特性JEP 443。有时候Java 的语法强制你给一个变量起名字但你在代码中根本用不到它。比如// 遍历集合但只用到了次数没用元素本身for(Stringitem:items){count;}这里的item变量从来没用过但你不得不给它起个名字否则编译不通过。JDK 21 允许你用下划线_作为变量名表示“这个变量我不需要请忽略它”。// 用未命名变量清晰表达我不需要这个值for(var_:items){count;}未命名变量的使用场景① 增强 for 循环当你只需要遍历次数不需要元素本身时。for(var_:items){System.out.println(处理中...);// 不需要用到 items 的元素}② try-catch 异常捕获当你捕获了异常但不需要它的详细信息时。try{someOperation();}catch(IOException_){// 不需要异常对象只需要知道发生了 IO 异常System.out.println(IO 操作失败请重试);}③ 赋值语句当你需要某个方法的副作用但不需要它的返回值时。// 从队列中移除前三个元素但只需要第一个varfirstqueue.poll();var_queue.poll();// 移除第二个不关心返回值var_queue.poll();// 移除第三个不关心返回值returnfirst;④ try-with-resources当你需要自动关闭资源但不需要引用它时。try(var_newFileInputStream(test.txt)){// 资源会自动关闭但我们不需要引用它}⚠️ 重要提醒未命名变量是预览特性Preview Feature默认是关闭的。要在 IDEA 中使用需要在运行配置的 JVM 参数中添加--enable-preview另外同一个作用域内可以多次使用_每次都是独立的未命名变量。5. 综合示例 —— 用上所有语法糖让我们把今天学的所有语法糖集中到一个例子中看看代码能变得多简洁packagecom.example.demo;importjava.util.List;/** * 演示 JDK 21 的语法糖var、文本块 formatted()、Record、未命名变量 */publicclassSyntaxSugarDemo{// 1. 定义一个 Record代替传统的 POJOpublicrecordStudent(Stringname,intage,Stringgrade){}publicstaticvoidmain(String[]args){// 2. 使用 var 简化变量声明varstudentsList.of(newStudent(张三,20,A),newStudent(李四,22,B),newStudent(王五,19,A));// 3. 使用未命名变量遍历但不使用元素varcount0;for(var_:students){count;// 只需要计数不需要元素本身}System.out.println(学生总数count);// 4. 使用文本块 formatted() 生成报告varreport 学生成绩报告 总人数%d ---------------- 姓名 年龄 等级 %s .formatted(students.size(),students.stream().map(s-String.format(%s %d %s,s.name(),s.age(),s.grade())).reduce((a,b)-a\nb).orElse(无数据));System.out.println(report);// 5. 未命名变量在 try-catch 中的使用try{varresult10/0;}catch(ArithmeticException_){System.out.println(计算错误除数不能为零);}}}这段代码虽然不长但用到了我们今天学的所有语法糖var让students和count的声明更简洁Record让Student的定义只有一行未命名变量_在遍历中表示“我不需要这个元素”文本块 formatted()让多行报告的构建清晰易读今天的总结今天我们认识了 JDK 21 中四个最实用的“语法糖”特性作用引入版本var局部变量类型推断让编译器帮你“猜”类型Java 10文本块 formatted()多行字符串模板优雅地填充变量Java 15Record一行代码定义纯数据类自动生成构造方法、访问器、toString等Java 16正式未命名变量_表示“这个变量我不需要”提升代码可读性Java 21预览这些特性有一个共同点它们不改变程序的运行逻辑只改变代码的写法。它们让 Java 代码从“啰嗦”变得“简洁”从“繁琐”变得“优雅”。你不需要立刻把所有代码都改成新写法但可以在合适的地方慢慢用起来。每用一次你就会少打几个字多一份清爽的感觉。动手试试把你之前写的“个人名片生成器”中的变量声明挑几个合适的改成var感受一下代码的变化。定义一个Record叫Book包含title、author、price三个字段。在main中创建几个实例打印它们观察toString()的自动生成效果。写一个方法遍历一个字符串列表但只需要统计数量不需要元素内容——用未命名变量_实现。用文本块 formatted()生成一封简单的邮件模板包含收件人、发件人、正文内容等占位符。下一篇文章我们将进入一个重要的阶段性总结——阶段实战命令行计算器。我们会把前面学到的所有知识变量、运算符、条件判断、循环、方法、数组、字符串综合起来写一个真正有用的工具。我们下一篇见。 获取本系列示例代码请访问 GitCode。