QUICKREF · LANGUAGE · JEP 394
instanceof 模式:判断与绑定合并
JDK 17 · 速查主题 03 / 06 · 预计 12 分钟
一句话instanceof 模式把类型判断和变量声明合并,省掉强转。它的关键是流作用域:变量只在编译器能证明匹配成功的路径上可见,而不是简单的块作用域。
学完你要能自己回答
- 模式变量为什么能在 && 右侧使用,在 || 右侧却不行?
- 提前 return / throw 为什么能让后续代码使用模式变量?
- 17 的 instanceof 模式与 21 的 record pattern 差在哪?
判断成功即绑定01
o instanceof String s 在判断类型的同时声明并绑定 s,匹配成功后 s 就是 String,不需要再强转。匹配失败时 s 不会被赋值。
static String normalize(Object o) {
if (o instanceof String s && !s.isBlank()) {
return s.strip().toLowerCase();
}
return "";
}
System.out.println("[" + normalize(" Hello ") + "]");
System.out.println(normalize(" ").isEmpty());
[hello] true
旧写法需要三步:instanceof 判断、强制转换、再访问。模式把三步压成一条声明,也消除了「判断错类型后强转」的机会。
模式变量不是
final,但不要在匹配后的分支里重新赋值;那会让读者误以为它仍代表原始匹配值。流作用域:由控制流决定可见性02
模式变量的作用域由流分析决定,而不是声明位置所在的块。编译器只允许在「确定匹配成功」的路径上访问它。
| 写法 | 右侧/后续能否使用 s | 原因 |
|---|---|---|
o instanceof String s && s.length() > 0 | 可以 | && 右侧只在左侧为 true 时求值 |
o instanceof String s || s.length() > 0 | 不可以 | 左侧为 false 时右侧仍会求值,s 未绑定 |
if (!(o instanceof String s)) return; 之后 | 可以 | 提前返回后,后续路径必然匹配成功 |
if (o instanceof String s) { ... } 块外 | 不可以 | 块外可能未匹配 |
static String upper(Object o) {
if (!(o instanceof String s)) {
return "not a string";
}
return s.toUpperCase();
}
System.out.println(upper("ok"));
System.out.println(upper(42));
OK not a string
同理,throw、break、continue 让后续路径不可达时,编译器也会承认模式变量已绑定。这不是简单的「if 块内可见」,而是对布尔短路和控制流的完整分析。
不要把流作用域当成块作用域。
if (o instanceof String s) { ... } 之外访问 s 一定编译失败,即使你觉得「逻辑上已经判断过了」。限制与边界03
- 不能对泛型参数做具体类型判断:
o instanceof List<String> l非法,只能写List<?> l。 null instanceof 任何类型都是 false,模式变量不会绑定。- 17 只支持类型模式,不支持 record 解构;
o instanceof Point(int x, int y)要等到 21。 - switch 里的类型模式在 17 是预览,instanceof 模式本身是正式能力,两者不要混为一谈。
- 模式变量可以与已有变量同名吗?不可以,同一作用域内重复声明会编译失败。
// 合法:泛型用通配符
if (o instanceof List<?> list && !list.isEmpty()) {
System.out.println(list.get(0));
}
// 非法:泛型参数不可具体化
// if (o instanceof List<String> list) { ... }
类型擦除决定了运行时没有 List<String> 的信息,编译器无法验证元素类型,因此禁止这种写法。需要元素类型时,只能绑定 List<?> 后逐个判断。
instanceof 模式解决的是「类型分支 + 强转」的噪音,不解决「数据形状解构」。后者是 record pattern 的职责,而它在 17 尚不存在。
什么时候用、什么时候不用04
| 场景 | 推荐写法 | 说明 |
|---|---|---|
| 单一类型判断 + 使用 | if (o instanceof String s) | 最直接,作用域清晰 |
| 多类型分支 | sealed + 预览 switch(17) | 类型模式 switch 仍是预览,生产代码谨慎 |
| 开放类型多分支 | if / else if 链 | 没有封闭集合,switch 无法穷尽 |
| 判断后只调用一个方法 | 模式 + 直接调用 | 避免为了强转多写一行 |
| 需要解构字段 | 21 的 record pattern | 17 只能强转后调访问器 |
instanceof 模式最适合「一个方法里处理一两种已知类型」的场景。分支超过三四种,且类型集合封闭时,才考虑 sealed + 模式 switch;否则 if 链更易读。
不要用 instanceof 模式替代多态。如果每个类型都有统一行为,把行为放进接口方法,而不是在调用方按类型分支。
版本边界BOUNDARY
这张表比 API 本身更值得记。写代码时先确认你的运行时版本。
| 特性 / API | 状态 | 说明 |
|---|---|---|
| instanceof 模式 | 16 正式 | JEP 394;JDK 14 / 15 预览。 |
| 流作用域 | 16 正式 | 由编译器流分析决定,不是块作用域。 |
| 泛型类型模式 | 不允许 | 类型擦除导致无法验证,只能使用通配符。 |
| record pattern | 21 正式 | 17 不支持解构 record。 |
| switch 类型模式 | 17 预览 | JEP 406;需要 --enable-preview。 |
费曼自检SELF CHECK
先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。
01. 为什么 o instanceof String s || s.length() > 0 编译失败?
|| 右侧在左侧为 false 时仍会求值,此时 s 没有绑定。编译器只在确定匹配成功的路径上允许访问模式变量,所以拒绝这段代码。
02. 提前 return 为什么能让后面的代码使用 s?
if (!(o instanceof String s)) return; 之后,能继续执行的路径必然满足匹配条件,流分析因此认为 s 已绑定。throw、break、continue 也有类似效果。03. instanceof 模式能判断 List<String> 吗?
不能。泛型参数在运行时被擦除,编译器无法验证元素类型,只允许
List<?> 这样的通配符类型。04. instanceof 模式与 switch 类型模式是什么关系?
两者共享类型模式的概念,但 instanceof 模式在 16 已正式,switch 类型模式在 17 仍是预览。17 的 switch 类型模式还需要 && 守卫,21 才改为 when 并正式。
学习进度
记录保存在本机浏览器 · 不上传