为什么在Java中将Integer与int进行比较会抛出NullPointerException?


81

观察这种情况令我非常困惑:

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它与包装的基元一起抛出而不是与真实的对象类型一起抛出时,它看起来不一致。


如果执行str.equals(“ 0”),则将获得NullPointerException。
Ash Burlaczenko 2010年

==运算符曾经曾经在任何情况下都针对NPE进行保存。对我来说,这只是另一个示例,它演示了在Java中引入自动装箱是一个坏主意。它只是由于许多原因而无法容纳,并且没有提供以前没有的任何内容。它只会使代码变得更短,同时却掩盖了实际情况。
x4u

我的想法是180度不同的。它们不应该到处都包含使用过的原始对象。然后让编译器优化并使用原语。这样就不会有任何混乱。
MrJacqes 2010年

Answers:


137

简短答案

关键是:

  • == 两个参考类型之间总是参考比较
    • 通常,例如,使用IntegerString,您可以equals改用
  • == 引用类型和数字原始类型之间的始终是数字比较
    • 引用类型将进行拆箱转换
    • 拆箱null总是抛出NullPointerException
  • 虽然Java对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数值相等算子==!=

如果相等运算符的操作数是两个数字类型的,或一个是数字类型的,并且另一种是可转换到数字类型,二进制数值提升时对操作数执行。如果操作数的提升类型为intlong,则执行整数相等性测试;否则,执行整数相等性测试。如果提升的类型为float or double,则执行浮点相等性测试。

请注意,二进制数值升级执行值集转换和装箱转换。

这说明:

Integer i = null;

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

这是来自有效Java 2nd Edition,第49项的摘录:首选原语而不是盒装原语

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

在某些地方您别无选择,只能使用盒装基元,例如泛型,但是否则您应该认真考虑使用盒装基元的决定是否合理。

参考文献

相关问题

相关问题


2
至于为什么 someRef == 0总是进行数字比较,这是一个非常合理的选择,因为比较两个盒装基元的引用几乎总是程序员的错误。在这种情况下默认引用比较是没有用的。
马克·彼得斯

2
为什么编译器不使用表达式代替表达式(myInteger == 0)(myInteger != null && myInteger == 0)而不是依靠开发人员编写此样板空检查代码?IMO我应该能够进行检查if (myBoolean),并且应该true仅在基础值明确确定的情况下才能进行评估true-我不必先进行空检查。
乔什·M


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

我是一个整数,而0是一个整数,所以实际上是这样的

i.intValue() == 0

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


4

Java的创建者可以将==运算符定义为直接对不同类型的操作数进行操作,在这种情况下,如果Integer I; int i;进行比较,则I==i;可能会问到“是否I持有Integer其值是i?的引用”这一问题,可以很容易地得到回答。即使I为null。不幸的是,Java不能直接检查不同类型的操作数是否相等。而是检查该语言是否允许将一个操作数的类型转换为另一个操作数的类型,如果允许,则将转换后的操作数与未转换的操作数进行比较。这种行为意味着变量xy以及z与一些类型的组合,它可能有x==yy==z,但x!=z[例如,x = 16777216f y = 16777216 z = 16777217]。这也意味着将比较I==i翻译为“将I转换为int,如果没有抛出异常,则将其与进行比较i”。


+1:实际上试图回答OP的问题:“为什么要这样设计?”
Martijn Courteaux

1
@MartijnCourteaux:许多语言似乎只为匹配类型的操作数定义运算符,并假定如果T可以隐式转换为U,则在可以接受U但不能接受T的情况下执行此类隐式转换时,应毫无怨言。如果不是这样的行为,语言可以定义==这样一种方式,如果在所有的情况下x==yy==zx==z毫无怨言所有编译,这三种比较会表现为一个等价关系。好奇的是,设计师推出了各种新颖的语言功能,但忽略了公理合规性。
2013年

1

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

由于这是不可能的(如您所指出的那样,它为null),因此NullPointerException将抛出该异常。


1

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

由于inull拆箱将抛出NullPointerException

推理是这样的:

JLS§15.21.1数值相等运算符==和!=的第一句内容如下:

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

显然i可以转换为数字类型,并且0是数字类型,因此对操作数执行二进制数字提升。

§5.6.2二进制数值促销说(除其他外):

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

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

如果r为null,则取消装箱转换将引发NullPointerException


0

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

public static Integer getNotNullIntValue(Integer value)
{
    if(value!=null)
    {
        return value;
    }
    return 0;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.