【问题标题】:Why comparing Integer with int can throw NullPointerException in Java?为什么在 Java 中比较 Integer 和 int 会抛出 NullPointerException?
【发布时间】:2018-07-01 16:33:27
【问题描述】:

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

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 对象类型一起抛出时,它看起来不一致。

【问题讨论】:

  • 如果你执行 str.equals("0"),你会得到一个 NullPointerException。
  • == 运算符曾经在任何情况下都被用来对抗 NPE。对我来说,这只是另一个例子,它展示了在 Java 中引入自动装箱是多么糟糕的主意。它只是因为很多原因不适合,并且没有提供以前没有的东西。它只会使代码更短,同时掩盖真正发生的事情。
  • 我的想法相差 180 度。他们不应该在任何地方都包含原始使用的对象。然后让编译器优化和使用原语。这样就不会有任何混乱。

标签: java nullpointerexception boxing


【解决方案1】:

简短的回答

关键点是这样的:

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

上述语句适用于任何给定的有效 Java 代码。有了这个理解,你提供的sn-p就没有任何不一致了。


长答案

以下是相关的 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 !=

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

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

这说明:

Integer i = null;

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

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

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

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

参考文献

相关问题

相关问题

【讨论】:

  • 至于 为什么 someRef == 0 始终是数字比较,这是一个非常合理的选择,因为比较两个盒装图元的引用几乎总是程序员错误。在这种情况下,默认引用比较是没有用的。
  • 为什么编译器不将表达式(myInteger == 0) 替换为(myInteger != null && myInteger == 0),而是依赖开发人员编写这个样板空检查代码? IMO 我应该能够检查 if (myBoolean) 并且当且仅当基础值特别是 true 时,它应该评估为 true - 我不应该先进行空检查。
【解决方案2】:

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

if ( i.intValue( ) == 0 )

因此,如果 inull,则为 NPE。

【讨论】:

    【解决方案3】:
    if (i == 0) {  //NullPointerException
       ...
    }
    

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

    i.intValue() == 0
    

    这会导致 nullPointer 因为 i 为空。对于String我们没有这个操作,所以也不例外。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • +1:实际上试图回答 OP 的问题“为什么要这样设计?”
      • @MartijnCourteaux:许多语言似乎只为匹配类型的操作数定义运算符,并假设如果 T 可以隐式转换为 U,则应在 U 可以随时执行此类隐式转换而不会抱怨被接受,但 T 不能。如果不是因为这样的行为,一种语言可以这样定义==,如果在所有情况下x==yy==zx==z都可以毫无怨言地编译,那么三个比较将表现为等价关系.好奇设计师推动了各种花哨的语言功能,却忽略了公理合规性。
      【解决方案5】:

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

      由于这是不可能的(当您划线时它为空),所以 NullPointerException 被抛出。

      【讨论】:

        【解决方案6】:

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

        由于inull,拆箱将抛出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 为 null,则拆箱转换会引发 NullPointerException

        【讨论】:

          【解决方案7】:

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

          public static Integer getNotNullIntValue(Integer value)
          {
              if(value!=null)
              {
                  return value;
              }
              return 0;
          }
          

          【讨论】:

            猜你喜欢
            • 2011-03-08
            • 2021-06-18
            • 1970-01-01
            • 2014-12-18
            • 2014-07-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多