Java新特性(三):Java 12 ~ 15
Java新特性(三):Java 12 ~ 15
导语:Java 12 ~ 15 是「预览特性试验田」阶段——switch 表达式、文本块、
instanceof模式匹配、record、密封类依次以预览形态登场,再逐步转正。面试考点集中在 switch 表达式的yield、文本块与原始字符串的区别、精确空指针提示,以及「预览特性如何启用」这个必被追问的机制性问题。
一、先理解「预览特性」机制(必问)
1. 正式 / 预览 / 孵化 / 实验特性有什么区别?(高频)
答: 这是新特性面试的「元问题」,答不好会显得只会背版本号。
| 类型 | 说明 | 是否进入 Java SE 规范 | 使用方式 |
|---|---|---|---|
| 正式(Standard) | 设计已定稿,向后兼容 | ✅ | 直接使用 |
| 预览(Preview) | 规格与实现基本完成,但未来可能修改或移除,只存在一个版本周期 | ❌ | --enable-preview --release N(编译与运行都要加) |
| 孵化(Incubator) | 以独立模块形式提供 API(如 jdk.incubator.vector),API 仍可能大改 | ❌ | --add-modules jdk.incubator.xxx |
| 实验(Experimental) | 功能完整但未成熟,需显式解锁 | ❌ | -XX:+UnlockExperimentalVMOptions -XX:+UseZGC |
预览特性的启用与限制:
# 编译(javac)与运行(java)都必须加 --enable-preview,且 --release 必须等于当前 JDK 版本
javac --enable-preview --release 14 Demo.java
java --enable-preview Demo关键纪律:预览特性一旦跨版本就必须重新编译(因为 class 文件里带有预览标记,下一个 JDK 版本拒绝加载),因此不能用于生产代码——面试中如果被问「你项目会用预览特性吗」,正确答案是「不会,至少等它转正」。
常见陷阱:
--release与-source/-target的区别。--release会同时约束语言级别、字节码版本和可用的标准 API,避免出现「用新 API 编译成功、丢到旧 JRE 上NoSuchMethodError」;-source/-target只管前两者。
二、Switch 表达式:从语句到表达式
2. switch 表达式与传统 switch 有什么区别?(必问)
答: 传统 switch 是语句,只能执行、不能产生值,且必须手动 break(漏写会「贯穿 fall-through」,是最经典的 bug 来源)。Java 12 引入预览、Java 14 正式转正的 switch 表达式带来了三个改进:
- 用
->箭头替代:,一个分支只执行一个表达式/代码块,不再贯穿; - 整个 switch 可以作为表达式返回值;
- 穷尽性检查:用于表达式时,编译器强制要求覆盖所有可能取值(enum / sealed 类型可省略
default,其他类型必须写)。
// ---------- 传统写法(语句 + break) ----------
int numLetters;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
numLetters = 6;
break; // 漏写 break 就会 fall-through
default:
numLetters = 0;
}
// ---------- JDK 14+:表达式写法 ----------
int letters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6; // 多标签合并,箭头分支不会贯穿
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
}; // 作为表达式使用必须有分号
// ---------- 需要多行逻辑时用 yield 返回值 ----------
String type = switch (code) {
case 1 -> "one";
case 2 -> {
System.out.println("处理 code=2"); // 分支内可以写多条语句
yield "two"; // 用 yield 返回该分支的值
}
default -> "unknown";
};常见追问:yield 和 return 有什么区别?
return结束整个方法并返回方法结果;yield只结束当前 switch 分支并产生该表达式的值,方法继续执行。yield不是标识符保留字之外的「关键字」——它仍是受限标识符,所以int yield = 1;在 Java 13 之后不再合法,但yield()作为方法名仍可调用。
关于 default:
enum Season { SPRING, SUMMER, AUTUMN, WINTER }
// enum 全覆盖时可以省略 default(编译器做穷尽性检查)
String tip = switch (season) {
case SPRING -> "春";
case SUMMER -> "夏";
case AUTUMN -> "秋";
case WINTER -> "冬";
}; // 此时新增一个枚举值会直接编译报错,属于「编译期兜底」,比 default 更安全
// 若覆盖不全会编译报错:the switch expression does not cover all possible input values面试加分点:switch 表达式 + 穷尽性检查是后续「模式匹配 switch」(Java 21 转正)与「密封类」组合起来实现「代数数据类型(ADT)」式建模的基础。
三、文本块(Text Blocks)
3. 文本块是什么?它是「原始字符串」吗?(高频)
答: 文本块用一对 """ 包裹多行字符串(JEP 378,Java 13 预览、Java 15 转正),解决了「HTML / SQL / JSON 拼接可读性极差」的问题。
它不是原始字符串(raw string)——这一点是重要考点:文本块仍然会处理转义字符(\n、\t、\"、\\ 照常生效),只是免去了手动写 \n 和 + 拼接的痛苦。需要绝对原样时,还可以用:
- 行尾反斜杠
\:表示「不换行」(抑制该行末尾的换行符); \s:表示一个空格,用于保护行尾空白不被自动去除。
// ---------- 以前的写法 ----------
String json = "{\n" +
" \"name\": \"java\",\n" +
" \"version\": 17\n" +
"}\n";
// ---------- 文本块 ----------
String json2 = """
{
"name": "java",
"version": 17
}
"""; // 开头 """ 后必须换行;内容自动去除「公共缩进」
System.out.println(json.equals(json2)); // true
// ---------- SQL ----------
String sql = """
SELECT id, name, age
FROM t_user
WHERE age > ?
AND city = ?
ORDER BY id DESC
""";
// ---------- 行尾 \ 抑制换行:拼成一行 ----------
String oneLine = """
hello \
world"""; // "hello world"
// ---------- \s 保护行尾空格 ----------
String keepSpace = """
a \s
b"""; // 第一行是 "a "(末尾空格保留)
// ---------- 格式化 ----------
String formatted = """
name=%s, age=%d""".formatted("张三", 18);文本块的规则要点:
"""后必须换行,否则编译错误;- 内容会做 「附带空白」剥离:自动去掉所有行共有的最小缩进,以及末尾多余空行;
- 缩进计算基于「结束分隔符
"""的位置」:结束符往左移,就能保留更多缩进; - 文本块内部可以直接写
"(单个双引号无需转义),连写三个"""才需要转义。
// 想保留缩进:把结束的 """ 往左靠
String s1 = """
line1
line2
"""; // 结果:"line1\n line2\n"编译产物:文本块是编译期常量(CONSTANT_String),也可以在常量表达式、注解、switch 标签等位置使用,没有任何运行时开销,与普通字符串拼接写法性能一致。
四、其他值得记住的特性
4. Helpful NullPointerException:精确空指针提示(JEP 358)
答: 以前 NPE 只有一行 NullPointerException,不知道到底哪个变量为 null。Java 14 起(Java 15 起默认开启),JVM 会分析字节码并给出精确到方法 / 字段的报错信息:
class A { B b; }
class B { C c; }
class C { int value; }
A a = new A();
System.out.println(a.b.c.value);// 旧信息:只有一行,靠猜
Exception in thread "main" java.lang.NullPointerException
// 新信息:直接告诉你是谁为 null
Exception in thread "main" java.lang.NullPointerException:
Cannot read field "c" because "a.b" is null# 参数(JDK 14 需手动打开,JDK 15+ 默认开启)
java -XX:+ShowCodeDetailsInExceptionMessages Demo
# 关闭:-XX:-ShowCodeDetailsInExceptionMessages注意:启用后错误信息会变长,且可能包含局部变量名(需
-g调试信息),生产环境对日志体积敏感时可以考虑关闭。
5. 其他版本要点速查
答:
Java 12(2019-03,非 LTS)
- Switch 表达式首次预览(JEP 325)、Shenandoah GC 实验性引入(JEP 189,Red Hat 主导的低延迟 GC);
- G1 可中止的混合收集(JEP 344):把混合 GC 拆成强制/可选部分,来不及就中止可选部分,保证不超停顿目标;
- G1 及时归还内存(JEP 346):空闲时把堆内存还给操作系统(K8s 容器里很实用);
- JVM Constants API(JEP 334):用
ClassDesc、MethodHandleDesc等描述字节码常量,服务字节码生成与编译器工具; - String 新方法:
indent(n)(缩进并规范换行)、transform(Function)(把字符串当管道输入); Files.mismatch(a, b):比较两个文件内容,返回首个不同字节位置,相同则返回-1;NumberFormat.getCompactNumberInstance():紧凑数字格式,1000显示为1K。
Java 13(2019-09,非 LTS)
- 文本块首次预览(JEP 355);
- Switch 表达式第二次预览,引入
yield(JEP 354); - 动态 CDS 归档(JEP 350):应用退出时自动归档已加载的类,免去手动生成类列表,加速下次启动;
- ZGC 归还未使用内存(JEP 351);
- Socket API 重写:底层默认换成
NioSocketImpl(用juc锁替代synchronized),-Djdk.net.usePlainSocketImpl=true可退回旧实现。
Java 14(2020-03,非 LTS)
- Switch 表达式正式转正(JEP 361);
instanceof模式匹配首次预览(JEP 305);record首次预览(JEP 359)、文本块第二次预览(JEP 368);- Helpful NPE 正式(JEP 358);
- 移除 CMS 垃圾收集器(JEP 363,Java 9 起已弃用);
jpackage打包工具(孵化):把应用打成 deb/rpm/msi/exe/dmg/pkg 等平台安装包;- ZGC 扩展到 macOS / Windows。
Java 15(2020-09,非 LTS)
- 文本块正式转正(JEP 378);
- 密封类首次预览(JEP 360)、
instanceof模式匹配第二次预览(JEP 375); - 隐藏类 Hidden Classes(JEP 371):不可被发现、生命周期短、可批量卸载,专为框架动态生成字节码设计(CGLIB、Groovy、Lambda 的隐藏实现类),避免类加载器泄漏(常见追问:
Lambda生成的类是不可发现的,就是靠它); - Shenandoah GC 转正(JEP 379);
- EdDSA 数字签名算法(JEP 339):基于 Ed25519,性能优于 ECDSA;
- 移除 Nashorn JS 引擎(Java 8 引入、11 弃用、15 删除);
- 禁用偏向锁(JEP 374,Java 15 起默认关闭并标记为废弃,后续版本移除);
CharSequence新增isEmpty()默认方法;TreeMap的putIfAbsent/computeIfAbsent等重写为一次遍历,保证 O(log n)。
五、小结
| 特性 | 首次预览 | 正式转正 | 一句话记住 |
|---|---|---|---|
| switch 表达式 | Java 12 | Java 14 | 箭头不贯穿、可返回值、穷尽性检查,块内用 yield |
| 文本块 | Java 13 | Java 15 | """ 多行字符串,不是原始字符串,仍会处理转义 |
instanceof 模式匹配 | Java 14 | Java 16 | 判断即转换,消灭强制类型转换 |
record | Java 14 | Java 16 | 不可变数据载体,自动生成 getter/equals/hashCode/toString |
| 密封类 | Java 15 | Java 17 | 控制「谁能继承我」,和 switch 穷尽性检查是绝配 |
| Helpful NPE | Java 14 | —— | 告诉你具体哪个字段/方法为 null(JDK 15 起默认开启) |
