【问题标题】:Booleans, conditional operators and autoboxing布尔值、条件运算符和自动装箱
【发布时间】:2011-04-22 09:04:52
【问题描述】:

为什么会抛出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
}

?

解决方案是顺便将false 替换为Boolean.FALSE 以避免null 被拆箱为boolean——这是不可能的。但这不是问题。问题是为什么? JLS 中是否有任何参考资料证实了这种行为,尤其是第二种情况?

【问题讨论】:

  • 哇,自动装箱是......呃......对于java程序员来说,惊喜的源泉,不是吗? :-)
  • 我遇到了类似的问题,令我吃惊的是它在 OpenJDK 虚拟机上失败,但在 HotSpot 虚拟机上工作......一次编写,随处运行!

标签: java nullpointerexception boolean conditional-operator autoboxing


【解决方案1】:

行:

    Boolean b = true ? returnsNull() : false;

在内部转换为:

    Boolean b = true ? returnsNull().booleanValue() : false; 

执行拆箱;因此:null.booleanValue() 将产生一个 NPE

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

编辑:我相信拆箱是由于第三个运算符是布尔类型,例如(添加了隐式转换):

   Boolean b = (Boolean) true ? true : false; 

【讨论】:

  • 当最终值是布尔对象时,为什么它会尝试像那样拆箱?
【解决方案2】:

来自Java Language Specification, section 15.25

  • 如果第二个和第三个之一 操作数是布尔类型,并且 另一个的类型是布尔类型, 然后是条件的类型 表达式是布尔值。

因此,第一个示例尝试调用Boolean.booleanValue(),以便按照第一条规则将Boolean 转换为boolean

在第二种情况下,第一个操作数是空类型,当第二个不是引用类型时,应用自动装箱转换:

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

【讨论】:

  • 这回答了第一种情况,但不是第二种情况。
  • 当其中一个值为null时可能存在例外。
  • @Erick:JLS 确认了吗?
  • @Erick:我认为它不适用,因为boolean 不是引用类型。
  • 我可以补充一下...这就是为什么您应该使三元的两侧具有相同的类型,并在必要时进行显式调用。即使你已经记住了规范并且知道会发生什么,下一个来阅读你的代码的程序员也可能不会。以我的拙见,如果编译器在这些情况下只产生一个错误消息,而不是做普通人难以预测的事情,那会更好。好吧,也许在某些情况下这种行为确实有用,但我还没有遇到过。
【解决方案3】:

区别在于returnsNull()方法的显式类型会影响编译时表达式的静态类型:

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

E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean)

参见 Java 语言规范,15.25 Conditional Operator ? : 部分

  • 对于 E1,第 2 和第 3 个操作数的类型分别为 Booleanboolean,因此该子句适用:

    如果第二个和第三个操作数之一是布尔类型,另一个是布尔类型,则条件表达式的类型是布尔类型。

    由于表达式的类型是boolean,第二个操作数必须强制转换为boolean。编译器将自动拆箱代码插入到第二个操作数(返回值returnsNull())以使其类型为boolean。这当然会导致在运行时返回来自 null 的 NPE。

  • 对于 E2,第 2 和第 3 个操作数的类型分别为 <special null type>(不是 E1 中的 Boolean!)和 boolean,因此不适用特定类型的子句(go read 'em!),所以最后的“否则" 子句适用:

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

    • S1 == <special null type>(见§4.1
    • S2 == boolean
    • T1 == box(S1) == <special null type>(请参阅§5.1.7 中的装箱转换列表中的最后一项)
    • T2 == 框(S2) == `布尔值
    • lub(T1, T2) == Boolean

    所以条件表达式的类型是Boolean,第三个操作数必须强制为Boolean。编译器为第三个操作数 (false) 插入自动装箱代码。第二个操作数不需要像E1那样自动拆箱,所以在返回null时不会自动拆箱NPE。


这个问题需要类似的类型分析:

Java conditional operator ?: result type

【讨论】:

  • 有道理......我认为。 §15.12.2.7 很痛苦。
  • 这很容易......但只是事后诸葛亮。 :-)
  • @BertF lub(T1,T2) 中的函数lub 代表什么?
  • @Geek - lub() - 最小上限 - 基本上是它们共同拥有的最接近的超类;由于 null(类型“特殊的 null 类型”)可以隐式转换(扩展)为任何类型,因此对于 lub(),您可以将特殊的 null 类型视为任何类型(类)的“超类”。
【解决方案4】:

我们可以从字节码中看出这个问题。在main的字节码第3行3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z,值null的装箱布尔值invokevirtual方法java.lang.Boolean.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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-06-18
    • 1970-01-01
    • 2018-06-05
    • 1970-01-01
    • 2011-09-27
    • 2011-03-27
    相关资源
    最近更新 更多