86

观察这种情况让我很困惑:

Integer i = null;
String str = null;

if (i == null) {   //Nothing happens
   ...                  
}
if (str == null) { //Nothing happens

}

if (i == 0) {  //NullPointerException
   ...
}
if (str == "0") { //Nothing happens
   ...
}

因此,正如我认为首先执行装箱操作(即 java 尝试从中提取 int 值null)并且比较操作具有较低的优先级,这就是引发异常的原因。

问题是:为什么在Java中以这种方式实现?为什么拳击比比较参考具有更高的优先级?或者为什么他们没有null在拳击前实施验证?

目前,当NullPointerException使用包装的原语抛出而不是使用真正的对象类型抛出时,它看起来不一致。

4

7 回答 7

143

简短的回答

关键点是这样的:

  • ==两个引用类型之间总是引用比较
    • 通常情况下,例如使用Integerand String,您会想要equals使用
  • ==引用类型和数字原始类型之间始终是数字比较
    • 引用类型将进行拆箱转换
    • 拆箱null总是抛出NullPointerException
  • 虽然 Java 对 有许多特殊处理String,但它实际上不是原始类型

上述语句适用于任何给定的有效Java 代码。有了这种理解,您提供的代码段中就没有任何不一致之处。


长答案

以下是相关的 JLS 部分:

JLS 15.21.3 参考等式运算符==!=

如果相等运算符的操作数既是引用类型又是null类型,则该操作是对象相等。

这解释了以下内容:

Integer i = null;
String str = null;

if (i == null) {   // Nothing happens
}
if (str == null) { // Nothing happens
}
if (str == "0") {  // Nothing happens
}

两个操作数都是引用类型,这==就是引用相等比较的原因。

这也解释了以下内容:

System.out.println(new Integer(0) == new Integer(0)); // "false"
System.out.println("X" == "x".toUpperCase()); // "false"

==实现数值相等,至少有一个操作数必须是数值类型

JLS 15.21.1 数值等式运算符==!=

如果相等运算符的操作数都是数字类型,或者一个是数字类型而另一个可以转换为数字类型,则对操作数执行二进制数字提升。如果操作数的提升类型是intor long,则执行整数相等测试;如果提升的类型是float or double`,则执行浮点相等测试。

请注意,二进制数字提升执行值集转换和拆箱转换。

这说明:

Integer i = null;

if (i == 0) {  //NullPointerException
}

这是Effective Java 2nd Edition,Item 49: Prefer primitives to boxed primitives的摘录:

总之,只要您有选择,就优先使用原语而不是盒装原语。原始类型更简单、更快。如果您必须使用盒装图元,请小心!自动装箱减少了使用装箱原语的冗长,但不会降低危险。当您的程序将两个装箱原语与==运算符进行比较时,它会进行身份比较,这几乎肯定不是您想要的。当您的程序进行涉及装箱和拆箱原语的混合类型计算时,它会拆箱,而当您的程序拆箱时,它可以抛出NullPointerException. 最后,当您的程序将原始值装箱时,可能会导致成本高昂且不必要的对象创建。

有些地方你别无选择,只能使用盒装原语,例如泛型,但否则你应该认真考虑使用盒装原语的决定是否合理。

参考

相关问题

相关问题

于 2010-07-28T12:28:07.367 回答
15

由于自动装箱,您的 NPE 示例等效于此代码:

if ( i.intValue( ) == 0 )

i因此,如果是NPE null

于 2010-07-28T12:30:40.390 回答
4
if (i == 0) {  //NullPointerException
   ...
}

i 是一个整数,而 0 是一个整数,所以真正要做的事情是这样的

i.intValue() == 0

这会导致 nullPointer 因为 i 为空。对于 String 我们没有这个操作,这就是为什么那里没有例外。

于 2010-07-28T12:33:23.630 回答
4

Java 的制造者可以将==运算符定义为直接作用于不同类型的操作数,在这种情况下,如果Integer I; int i;进行比较,则I==i;可能会问“是否I持有对Integer值为的对象的引用i?”——这个问题可以毫无困难地回答即使I是空的。不幸的是,Java 并不直接检查不同类型的操作数是否相等;相反,它检查语言是否允许将任一操作数的类型转换为另一个操作数的类型,并且如果允许,则将转换后的操作数与未转换的操作数进行比较。这种行为意味着对于变量x, y, 和z某些类型的组合,可能有x==yand y==zbutx!=z[例如 x=16777216f y=16777216 z=16777217]。这也意味着比较I==i被翻译为“将 I 转换为 an int,如果不引发异常,则将其与i.”进行比较。

于 2013-11-26T20:41:06.440 回答
1

i == 0Java 中会尝试进行自动拆箱并进行数值比较(即“存储在包装器对象中i的值是否与值相同0?”)。

由于inull拆箱会抛出一个NullPointerException.

推理是这样的:

JLS § 15.21.1 Numerical Equality Operators == 和 !=的第一句如下所示:

如果相等运算符的操作数都是数字类型,或者一个是数字类型而另一个是可转换的(第 5.1.8 节)为数字类型,则对操作数执行二进制数字提升(第 5.6.2 节)。

Clearlyi可转换为数值类型并且0是数值类型,因此对操作数执行二进制数值提升。

§ 5.6.2 二进制数字提升说(除其他外):

如果任何操作数是引用类型,则执行拆箱转换(第 5.1.8 节)。

§ 5.1.8 拆箱转换说(除其他外):

如果r为 null,则拆箱转换会抛出NullPointerException

于 2010-07-28T12:32:16.453 回答
1

这是因为 Java 的自动装箱功能。编译器检测到,在比较的右侧,您正在使用原始整数,并且还需要将包装器 Integer 值拆箱为原始 int 值。

由于这是不可能的(正如您所列出的那样,它是空的)所以NullPointerException被抛出。

于 2010-07-28T12:29:03.690 回答
0

只需编写一个方法并调用它即可避免 NullPointerException。

public static Integer getNotNullIntValue(Integer value)
{
    if(value!=null)
    {
        return value;
    }
    return 0;
}
于 2020-08-09T10:20:16.540 回答