观察这种情况让我非常困惑:
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尝试从null
中提取int值)并且比较操作具有较低的优先级,这就是抛出异常的原因。
问题是:为什么在Java中以这种方式实现?为什么拳击优先于比较参考?或者为什么他们没有在拳击前对null
进行验证?
当使用包装的基元抛出NullPointerException
并且不使用 true 对象类型抛出它时,它看起来不一致。
答案 0 :(得分:131)
关键是:
==
始终是引用比较
Integer
和String
,您需要使用equals
代替==
始终是数字比较
null
始终抛出NullPointerException
String
有许多特殊处理,但实际上它不是原始类型以上语句适用于任何给定的有效 Java代码。根据这种理解,您提供的代码段中没有任何不一致。
以下是相关的JLS部分:
JLS 15.21.3 Reference Equality Operators
==
and!=
如果等于运算符的操作数都是引用类型或 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 Numerical Equality Operators
==
and!=
如果等于运算符的操作数 的数值类型,或一个的数字类型,另一个可转换为数字类型,对操作数执行二进制数字提升。如果提升的操作数类型是
int
或long
,则执行整数相等测试;如果提升的类型是float or
double`,则执行浮点相等测试。请注意,二进制数字促销会执行值集转换和拆箱转换。
这解释了:
Integer i = null;
if (i == 0) { //NullPointerException
}
以下摘录自 Effective Java 2nd Edition,第49项:首选原语为盒装基元:
总之,只要有选择,就可以优先使用原始元素。原始类型更简单,更快捷。如果你必须使用盒装基元,小心!自动装箱减少了使用盒装基元的冗长,但没有降低危险。当您的程序将两个盒装基元与
==
运算符进行比较时,它会进行身份比较,这几乎肯定不是您想要的。当您的程序执行涉及盒装和未装箱原语的混合类型计算时,它会进行拆箱,当您的程序取消装箱时,它可以抛出NullPointerException
。最后,当您的程序框原始值时,它可能导致代价高昂且不必要的对象创建。
有些地方你别无选择,只能使用盒装基元,例如:泛型,但除此之外你应该认真考虑是否有合理使用盒装基元的决定。
Integer
类型转换为int
类型”r
为null
,则取消装箱转化会引发NullPointerException
”==
and !=
==
and !=
Integers
in Java does auto-unboxing occur? ==
but not equals()
? 答案 1 :(得分:12)
由于 autoboxing ,您的NPE示例等同于此代码:
if ( i.intValue( ) == 0 )
如果i
为null
,则为NPE。
答案 2 :(得分:3)
if (i == 0) { //NullPointerException
...
}
我是一个整数,0是一个整数,所以真正做的就是这样的
i.intValue() == 0
这会导致nullPointer因为i为null。对于String,我们没有这个操作,那就是为什么也不例外。
答案 3 :(得分:3)
Java的制造者可以定义==
运算符直接对不同类型的操作数进行操作,在这种情况下,Integer I; int i;
比较I==i;
可以提出问题“I
1}}保留对值为Integer
的{{1}}的引用? - 即使i
为空也可以毫无困难地回答的问题。遗憾的是,Java并没有直接检查不同类型的操作数是否相等;相反,它检查语言是否允许将任一操作数的类型转换为另一个操作数的类型,如果是,则将转换的操作数与未转换的操作数进行比较。这种行为意味着对于具有某些类型组合的变量I
,x
和y
,可以有z
和x==y
但y==z
} [例如x = 16777216f y = 16777216 z = 16777217]。这也意味着比较x!=z
被翻译为“将我转换为I==i
,如果不引发异常,则将其与int
进行比较。”
答案 4 :(得分:1)
这是因为Javas autoboxing 功能。编译器检测到,在比较的右侧,您正在使用原始整数,并且需要将包装器Integer值拆分为原始int值。
由于那是不可能的(当你排除它时它是空的)NullPointerException
被抛出。
答案 5 :(得分:1)
在i == 0
中,Java将尝试进行自动拆箱并进行数值比较(即“存储在i
引用的包装器对象中的值与值0
相同?“)。
由于i
为null
,拆箱会抛出NullPointerException
。
推理是这样的:
JLS § 15.21.1 Numerical Equality Operators == and !=的第一句话是这样的:
如果等于运算符的操作数都是数字类型,或者一个是数字类型而另一个是可转换的(第5.1.8节)是数字类型,则对操作数执行二进制数字提升(第5.6.2节) )。
显然i
可转换为数字类型,0
是数字类型,因此对操作数执行二进制数字提升。
§ 5.6.2 Binary Numeric Promotion说(除其他外):
如果任何操作数属于引用类型,则执行拆箱转换(第5.1.8节)。
§ 5.1.8 Unboxing Conversion说(除其他外):
如果 r 为空,则取消装箱转化会引发
NullPointerException
答案 6 :(得分:0)
只需编写一个方法并调用它即可避免NullPointerException。
public static Integer getNotNullIntValue(Integer value)
{
if(value!=null)
{
return value;
}
return 0;
}