把工具链换到 JDK 28 之后,一段以前能编过的三元表达式突然挂了:(b ? typed : raw).get(0) 返回 Integer 时,javac 报 Object cannot be converted to Integer。同一行在 JDK 27.0.1.0.3 上 exit=0,在 28-ea+21-1707 上 exit=1。开关不在 --release,在编译器版本。
根因是 JDK-8385925。standalone 三元 / switch 混用 raw 和参数化类型时,旧 javac 用赋值兼容(含 unchecked)挑结果类型,操作数顺序能决定能不能过;JDK 28 改回按子类型关系,两端都落到 raw List,get(0) 就是 Object。
谁会撞上
Spring Boot 服务里还留着 raw List 的老缓存、老 JDBC/反射工具,旁边已经是 List<Integer> / List<String>。业务代码用三元或 switch 在两条路径间挑一个列表,再直接 .get(0) 赋给具体类型。
更隐蔽的是「只换 CI 的 JDK」。pom 仍写 release 21,本机 JDK 27 一切绿灯;流水线升到 28,编译阶段直接红。错误信息看不出「顺序曾经救过你」,只会说 Object 转不了 Integer。
raw 在前的写法两边本来就会挂。只有「参数化在前、raw 在后」那一支,是 27 能过、28 不过。
现场长什么样
最小探针(官方发行说明同款):
import java.util.List;
class TernaryParamFirst {
Integer first(boolean b, List raw, List<Integer> parameterized) {
return (b ? parameterized : raw).get(0);
}
}
实测:
===== TernaryParamFirst =====
--- JDK 27.0.1.0.3+1-2 ---
javac 27.0.1.0.3
exit=0
--- JDK 28-ea+21-1707 ---
javac 28-ea
probes/TernaryParamFirst.java:4: error: incompatible types: Object cannot be converted to Integer
return (b ? parameterized : raw).get(0);
^
1 error
exit=1
把操作数对调成 raw 在前,两边都挂:
===== TernaryRawFirst (both fail) =====
--- JDK 27.0.1.0.3+1-2 ---
probes/TernaryRawFirst.java:4: error: incompatible types: Object cannot be converted to Integer
return (b ? raw : parameterized).get(0);
^
1 error
exit=1
--- JDK 28-ea+21-1707 ---
probes/TernaryRawFirst.java:4: error: incompatible types: Object cannot be converted to Integer
return (b ? raw : parameterized).get(0);
^
1 error
exit=1
standalone switch 同一套规则。参数化分支写在前面时,27 过、28 挂:
===== SwitchParamFirst =====
--- JDK 27.0.1.0.3+1-2 ---
exit=0
--- JDK 28-ea+21-1707 ---
probes/SwitchParamFirst.java:7: error: incompatible types: Object cannot be converted to Integer
}).get(0);
^
1 error
exit=1
怎么确认
先固定两套 javac,都加 --release 21,排除「语言级别变了」的猜测。上面对照已经说明:release 不变,换编译器版本就分叉。
再落到 Spring Boot 4.1.2 工程。服务里留一段「参数化在前」的挑选逻辑:
@SuppressWarnings("rawtypes")
public Integer pickParamFirst(boolean useTyped, List raw, List<Integer> typed) {
return (useTyped ? typed : raw).get(0);
}
JDK 27.0.1.0.3 上 mvn -DskipTests clean compile exit=0。同一份源码换 28-ea+21:
[INFO] Compiling 3 source files with javac [debug parameters release 21] to target/classes
[ERROR] COMPILATION ERROR :
[ERROR] LegacyListPicker.java:[12,44] incompatible types: java.lang.Object cannot be converted to java.lang.Integer
[ERROR] LegacyListPicker.java:[20,15] incompatible types: java.lang.Object cannot be converted to java.lang.Integer
[INFO] BUILD FAILURE
两行分别对应三元和 switch。maven-compiler-plugin 3.15.0,release 仍是 21。
连带断点
- 只改操作数顺序不是修复。28 上两种顺序都按 raw
List定型,对调只会从「碰巧能过」变成「两边都不能过」的另一种写法。 @SuppressWarnings("rawtypes")/unchecked压不住这条。这是结果表达式类型,不是警告。- IDE 若仍绑着 JDK 27 的 javac,本地绿、CI 红,会非常像「流水线脏了」。对齐工具链版本再比。
- 和常量三元
null在前调length崩池那条不是一回事。那条是常量折叠/常量池断言;这条是 raw 与参数化混用时的类型判定。
升级怎么选
优先消 raw。接口、缓存、工具方法改成 List<?> 或具体参数化类型,三元两边类型一致,问题直接消失。
短期过编译:先接到 List<?> / Object,再显式转换。
Integer first(boolean b, List raw, List<Integer> parameterized) {
Object o = (b ? parameterized : raw).get(0);
return (Integer) o;
}
这版在 27.0.1.0.3 和 28-ea+21 上 javac 都是 exit=0。跑通后的安全路径用 Spring Boot 4.1.2 + 28-ea+21 起服务:
{"java.version":"28-ea","useTyped":true,"value":1,"spring.boot.version":"4.1.2"}
{"java.version":"28-ea","useTyped":false,"value":7,"spring.boot.version":"4.1.2"}
升级清单可以加一条:全库搜 raw List/Map 出现在三元或 switch 结果里、又立刻按具体类型使用的调用点。先在 JDK 28 EA 上编一轮,比等到 GA 再堵 CI 便宜。
参考:OpenJDK 发行说明 javac Correctly Types Conditional and Switch Expressions Mixing Raw and Parameterized Types (JDK-8385925),以及 JBS JDK-8385925(Resolved in Build b17)。
一起交流
分享你的思考,让讨论更进一步。