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()
。这当然会导致NPE从运行时返回的boolean
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
时,不会自动取消装箱NPEnull
- S1==
该问题需要类似类型的分析:
我们可以从字节码中看出这个问题。在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虚拟机上工作。。。写一次,到处跑!有道理。。。我想。这是一种痛苦,很容易。。。但只是事后来看。:-)@BertFlub(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