Java 布尔、条件运算符和自动装箱

Java 布尔、条件运算符和自动装箱,java,nullpointerexception,boolean,conditional-operator,autoboxing,Java,Nullpointerexception,Boolean,Conditional Operator,Autoboxing,为什么会抛出NullPointerException public static void main(String[] args) throws Exception { Boolean b = true ? returnsNull() : false; // NPE on this line. System.out.println(b); } public static Boolean returnsNull() { return null; } 而这不是 publi

为什么会抛出
NullPointerException

public static void main(String[] args) throws Exception {
    Boolean b = true ? returnsNull() : false; // NPE on this line.
    System.out.println(b);
}

public static Boolean returnsNull() {
    return null;
}
而这不是

public static void main(String[] args) throws Exception {
    Boolean b = true ? null : false;
    System.out.println(b); // null
}
?

解决方案是用
Boolean.false
替换
false
,以避免
null
被取消绑定到
Boolean
——这是不可能的。但这不是问题所在。问题是为什么?JLS中是否有任何参考资料证实了这种行为,尤其是第二种情况?

该行:

    Boolean b = true ? returnsNull() : false;
内部转换为:

    Boolean b = true ? returnsNull().booleanValue() : false; 
进行拆箱;因此:
null.booleanValue()
将产生一个NPE

这是使用自动装箱时的主要陷阱之一。这种行为确实在中有记录

编辑:我认为取消装箱是由于第三个运算符是布尔类型,如(添加了隐式cast):

发件人:

  • 如果第二个和第三个 操作数的类型为布尔型,且 另一个的类型为布尔类型, 然后是条件的类型 表达式是布尔型的
因此,第一个示例尝试调用
Boolean.booleanValue()
,以便根据第一条规则将
Boolean
转换为
Boolean

在第二种情况下,第一个操作数为null类型,而第二个操作数不是引用类型,因此应用自动装箱转换:

  • 否则,第二个和第三个 操作数的类型为S1和S2 分别地设T1为 拳击比赛的结果 转换为S1,设T2为 类型,该类型是应用装箱的结果 转换为S2。类型 条件表达式是结果 应用捕获转换的方法 (§5.1.10)至润滑油(T1、T2)(§15.12.2.7)

不同之处在于
returnsnall()
方法的显式类型会影响编译时表达式的静态类型:

E1: `true ? returnsNull() : false` - boolean (auto-unboxing 2nd operand to boolean)

E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean)
请参阅Java语言规范,第节

  • 对于E1,第二个和第三个操作数的类型分别为
    布尔
    布尔
    ,因此本条款适用:

    如果第二个和第三个操作数中的一个为boolean类型,另一个为boolean类型,则条件表达式的类型为boolean

    由于表达式的类型为
    布尔
    ,因此第二个操作数必须强制为
    布尔
    。编译器将自动取消装箱代码插入第二个操作数(返回值为
    returnsnall()
    ),使其类型为
    boolean
    。这当然会导致NPE从运行时返回的
    null

  • 对于E2,第二个和第三个操作数的类型分别是
    (而不是像E1中那样
    布尔值
    )和
    布尔值
    ,因此没有特定的类型子句适用(),因此最后的“否则”子句适用:

    否则,第二个和第三个操作数分别为S1和S2类型。设T1为将装箱转换应用于S1时产生的类型,设T2为将装箱转换应用于S2时产生的类型。条件表达式的类型是将捕获转换(§5.1.10)应用于lub(T1,T2)(§15.12.2.7)的结果

    • S1==
      (请参阅)
    • S2==
      boolean
    • T1==框(S1)=
      (请参阅中装箱转换列表中的最后一项)
    • T2==框(S2)=`Boolean
    • lub(T1,T2)=
      Boolean
    因此,条件表达式的类型是
    Boolean
    ,第三个操作数必须强制为
    Boolean
    。编译器为第三个操作数插入自动装箱代码(
    false
    )。第二个操作数不需要像
    E1
    中那样自动取消装箱,因此当返回
    null
    时,不会自动取消装箱NPE


该问题需要类似类型的分析:


我们可以从字节码中看出这个问题。在main字节码的第3行,
3:invokevirtual#3//Method java/lang/booleanValue:()Z
,值null的装箱布尔值,
invokevirtual
方法
java.lang.booleanValue
,它当然会抛出NPE

    public static void main(java.lang.String[]) throws java.lang.Exception;
      descriptor: ([Ljava/lang/String;)V
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=2, locals=2, args_size=1
           0: invokestatic  #2                  // Method returnsNull:()Ljava/lang/Boolean;
           3: invokevirtual #3                  // Method java/lang/Boolean.booleanValue:()Z
           6: invokestatic  #4                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
           9: astore_1
          10: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
          13: aload_1
          14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
          17: return
        LineNumberTable:
          line 3: 0
          line 4: 10
          line 5: 17
      Exceptions:
        throws java.lang.Exception

    public static java.lang.Boolean returnsNull();
      descriptor: ()Ljava/lang/Boolean;
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=1, locals=0, args_size=0
           0: aconst_null
           1: areturn
        LineNumberTable:
          line 8: 0

当最终值是一个布尔对象时,为什么它会这样尝试取消装箱?这回答了第一种情况,但不是第二种情况。当其中一个值为
null
时可能会有例外@Erick:JLS确认了这一点吗?@Erick:我认为它不适用,因为
布尔
不是引用类型。我可以补充一下。。。这就是为什么您应该使三元组的两侧都具有相同的类型,必要时可以显式调用。即使您已经记住了规范,并且知道会发生什么,下一个来阅读您的代码的程序员也可能不会。依我的拙见,如果编译器在这些情况下只生成一条错误消息,而不是做普通人难以预测的事情,那就更好了。嗯,也许有些情况下这种行为真的很有用,但我还没有做到。哇,自动装箱是一种无穷无尽的。。。呃。。。这对java程序员来说是个惊喜,不是吗?:-)我有一个类似的问题,让我惊讶的是,它在OpenJDK虚拟机上失败了,但在HotSpot虚拟机上工作。。。写一次,到处跑!有道理。。。我想。这是一种痛苦,很容易。。。但只是事后来看。:-)@BertF
lub(T1,T2)
中的函数
lub
代表什么?@Geek-lub()-最小上界-基本上是它们共同拥有的最接近的超类;由于NULL(类型“特殊null类型”)可以隐式转换(加宽)到任何类型,所以可以考虑特殊null类型是任何类型(类)的Lub()的“超类”。
    public static void main(java.lang.String[]) throws java.lang.Exception;
      descriptor: ([Ljava/lang/String;)V
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=2, locals=2, args_size=1
           0: invokestatic  #2                  // Method returnsNull:()Ljava/lang/Boolean;
           3: invokevirtual #3                  // Method java/lang/Boolean.booleanValue:()Z
           6: invokestatic  #4                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
           9: astore_1
          10: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
          13: aload_1
          14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
          17: return
        LineNumberTable:
          line 3: 0
          line 4: 10
          line 5: 17
      Exceptions:
        throws java.lang.Exception

    public static java.lang.Boolean returnsNull();
      descriptor: ()Ljava/lang/Boolean;
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=1, locals=0, args_size=0
           0: aconst_null
           1: areturn
        LineNumberTable:
          line 8: 0