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

发布于 2024-09-26 20:11:54 字数 635 浏览 1 评论 0 原文

为什么这会抛出 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 中是否有任何参考文献证实了这种行为,尤其是第二种情况?

Why does this throw 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;
}

while this doesn't

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

?

The solution is by the way to replace false by Boolean.FALSE to avoid null being unboxed to boolean --which isn't possible. But that isn't the question. The question is why? Are there any references in JLS which confirms this behaviour, especially of the 2nd case?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

暮年 2024-10-03 20:11:54

区别在于 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 条件运算符 ? :

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

    <块引用>

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

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

  • 对于E2,第二个和第三个操作数的类型是<特殊空类型>(不是E1中的Boolean!)和boolean 分别,因此不适用特定的类型子句 (去读他们!),所以最后的“否则”子句适用:

    <块引用>

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

    • S1 == <特殊空类型>(请参阅§4.1)
    • S2 == 布尔值
    • T1 == box(S1) == <特殊空类型>(请参阅 §5.1.7)
    • T2 == box(S2) == `布尔值
    • lub(T1, T2) == 布尔值

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


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

Java条件运算符?:结果类型

The difference is that the explicit type of the returnsNull() method affects the static typing of the expressions at compile time:

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

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

See Java Language Specification, section 15.25 Conditional Operator ? :

  • For E1, the types of the 2nd and 3rd operands are Boolean and boolean respectively, so this clause applies:

    If one of the second and third operands is of type boolean and the type of the other is of type Boolean, then the type of the conditional expression is boolean.

    Since the type of the expression is boolean, the 2nd operand must be coerced to boolean. The compiler inserts auto-unboxing code to the 2nd operand (return value of returnsNull()) to make it type boolean. This of course causes the NPE from the null returned at run-time.

  • For E2, types of the 2nd and 3rd operands are <special null type> (not Boolean as in E1!) and boolean respectively, so no specific typing clause applies (go read 'em!), so the final "otherwise" clause applies:

    Otherwise, the second and third operands are of types S1 and S2 respectively. Let T1 be the type that results from applying boxing conversion to S1, and let T2 be the type that results from applying boxing conversion to S2. The type of the conditional expression is the result of applying capture conversion (§5.1.10) to lub(T1, T2) (§15.12.2.7).

    • S1 == <special null type> (see §4.1)
    • S2 == boolean
    • T1 == box(S1) == <special null type> (see last item in list of boxing conversions in §5.1.7)
    • T2 == box(S2) == `Boolean
    • lub(T1, T2) == Boolean

    So the type of the conditional expression is Boolean and the 3rd operand must be coerced to Boolean. The compiler inserts auto-boxing code for the 3rd operand (false). The 2nd operand doesn't need the auto-unboxing as in E1, so no auto-unboxing NPE when null is returned.


This question needs a similar type analysis:

Java conditional operator ?: result type

暖树树初阳… 2024-10-03 20:11:54

该行:

    Boolean b = true ? returnsNull() : false;

在内部转换为:

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

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

这是使用自动装箱时的主要陷阱之一。此行为确实记录在 5.1.8 JLS< /a>

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

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

The line:

    Boolean b = true ? returnsNull() : false;

is internally transformed to:

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

to perform the unboxing; thus: null.booleanValue() will yield a NPE

This is one of the major pitfalls when using autoboxing. This behavior is indeed documented in 5.1.8 JLS

Edit: I believe the unboxing is due to the third operator being of boolean type, like (implicit cast added):

   Boolean b = (Boolean) true ? true : false; 
腹黑女流氓 2024-10-03 20:11:54

来自 Java 语言规范第 15.25 节

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

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

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

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

From Java Language Specification, section 15.25:

  • If one of the second and third
    operands is of type boolean and the
    type of the other is of type Boolean,
    then the type of the conditional
    expression is boolean.

So, the first example tries to call Boolean.booleanValue() in order to convert Boolean to boolean as per the first rule.

In the second case the first operand is of the null type, when the second is not of the reference type, so autoboxing conversion is applied:

  • Otherwise, the second and third
    operands are of types S1 and S2
    respectively. Let T1 be the type that
    results from applying boxing
    conversion to S1, and let T2 be the
    type that results from applying boxing
    conversion to S2. The type of the
    conditional expression is the result
    of applying capture conversion
    (§5.1.10) to lub(T1, T2) (§15.12.2.7).
寂寞陪衬 2024-10-03 20:11:54

我们从字节码就可以看出这个问题。 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

We can see this problem from byte code. At line 3 of main's byte code, 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z, the boxing Boolean of value null, invokevirtual the method java.lang.Boolean.booleanValue, it will throw NPE of course.

    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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文