"flag ? 0 : nullableInteger 不管 flag 是什么值都会 NPE"是流传很广的说法。真实反编译字节码发现,至少在 Java 17 上,这句话是不准确的——真实结果比传言更符合直觉。
上一篇:String.hashCode() 真实公式:真实造出一对内容完全不同却哈希相同的字符串
在验证有争议的三元表达式场景之前,先真实确认两个大家公认、确实会踩坑的拆箱陷阱。
真实抛出 NullPointerException(拆箱 null 相当于调用 null.intValue())
真实抛出 NullPointerException(方法返回 null,+ 运算符触发了自动拆箱)
这两个场景毫无争议:int unboxed = boxedNull; 和 maybeNull(true) + 1 都会真实、无条件地触发拆箱,boxedNull/maybeNull(true) 只要是 null,就一定真实抛出 NullPointerException。
流传很广的说法是:flag ? 0 : nullableInteger 这种写法,因为两个分支类型不统一(一个 int 字面量,一个 Integer),编译器会强制把整个表达式的类型定成 int,导致哪怕走的是 0 这个分支,nullableInteger 也会被提前拆箱。真实运行两种 flag 取值看看。
flag=true 时 result = 0(真实没有抛异常) flag=false 时真实抛出 NullPointerException(这次真的选中了 null 分支)
真实结果和"无条件都会 NPE"的说法不一致——flag=true 时选中的是 0 这个分支,真实完全没有触发异常;只有 flag=false、真正选中 nullableInteger 这个分支时,才真实抛出了 NullPointerException。这个结果本身其实更符合直觉("没被选中的分支不该被求值"),只是和网上一些流传的说法对不上。
反编译字节码,看编译器到底生成了什么样的控制流。
57: iload_2 58: ifeq 65 61: iconst_0 62: goto 69 65: aload_3 66: invokevirtual #27 // Method java/lang/Integer.intValue:()I 69: istore 4
字节码真实显示的是一个标准的 if-else 结构:先判断 flag(ifeq),为真就直接 iconst_0 压入常量 0 然后跳过后面(goto 69);为假才会走到 aload_3 加载 nullableInteger、再 invokevirtual Integer.intValue() 真实拆箱。intValue() 这条调用只出现在 flag 为假的分支里,不会在 flag 为真时被执行——这就是真实测出"选中 0 分支不报错"的字节码层面的原因。
每一步高亮的指令,都是真实用 javap -c 反编译这段代码得到的,不是按语义推测的。