等号检查后使用Long的NullPointerException

时间:2012-10-02 08:17:25

标签: java casting nullpointerexception long-integer

这让我失望。

如果您有一个Java Long变量,并且使用==运算符检查与原始值的相等性,则值的运行时类型将更改为原始long。

随后检查变量的空值,然后抛出意外的NullPointerException。

所以在测试类中:

public class LongDebug {

public static void main(String[] args) {
    Long validValue = 1L; 
    Long invalidValue = -1L;
    Long nullValue = null;

    System.out.println("\nTesting the valid value:");
    testExpectedBehaviour(validValue);
    testUnExpectedBehaviour(validValue);

    System.out.println("\nTesting the invalid value:");
    testExpectedBehaviour(invalidValue);
    testUnExpectedBehaviour(invalidValue);

    System.out.println("\nTesting the null value:");
    testExpectedBehaviour(nullValue);
    testUnExpectedBehaviour(nullValue);
}

/**
 * @param validValue
 */
private static void testExpectedBehaviour(Long value) {
    if (value == null || value == -1) System.out.println("Expected: The value was null or invalid");
    else System.out.println("Expected: The value was valid");
}

private static void testUnExpectedBehaviour(Long value) {
    try {
        if (value == -1 || value == null) System.out.println("Unexpected: The value was null or invalid");
        else System.out.println("Unexpected: The value was valid");
    } catch (NullPointerException e) {
        System.out.println("Unexpected: The system threw an unexpected NullPointerException");
    }
}
}

我得到的结果是:

Testing the valid value:
Expected: The value was valid
Unexpected: The value was valid

Testing the invalid value:
Expected: The value was null or invalid
Unexpected: The value was null or invalid

Testing the null value:
Expected: The value was null or invalid
Unexpected: The system threw an unexpected NullPointerException

这是规格还是JDK中的错误?

4 个答案:

答案 0 :(得分:4)

这是问题所在:

value == -1 || value == null

表达式从左到右进行评估,由于Long必须首先取消装箱,因此JVM会将其转换为:

value.longValue() == -1 || value == null

NullPointerExceptionvalue参数时,value.longValue()会引发null。它永远不会达到表达式的第二部分。

虽然订单不同,但它有效:

value == null || value == -1

因为如果valuenull,则第二部分(NullPointerException value时可能导致null)因布尔表达式而永远不会被执行short-circuit evaluation

  

这是规格还是JDK中的错误?

当然这不是错误。原始值包装器未装箱的方式符合规范(5.1.8. Unboxing Conversion):

  
      
  • 如果rLong类型的引用,则取消装箱转化会将r转换为r.longValue()
  •   

应用拆箱后,其余为标准Java。

答案 1 :(得分:2)

  

这是规格还是JDK中的错误?

这很正常。如果取消引用null的引用,则应该获得NullPointerException。这意味着如果您要检查null,则必须在此之前进行检查。检查之后是毫无意义和令人困惑的。

if (value == -1 || value == null)

相同
if (value.longValue() == -1 || value == null)

并且表达式的第一部分在第二部分运行之前抛出NPE。如果第一部分没有失败,则第二部分必须是假的。

答案 2 :(得分:2)

这是规范的一部分,特别是5.6.2. Binary Numeric Promotion5.1.8. Unboxing Conversion。相关部分:

  

5.6.2。二进制数字促销

     

当运算符将二进制数字提升应用于一对操作数时,每个操作数必须表示可转换为数字类型的值,以下规则适用:

     
      
  1. 如果任何操作数属于引用类型,则进行拆箱转换(第5.1.8节)。
  2.         

    [...]

         

    对某些运算符的操作数执行二进制数字提升:

         

    [...]

         
        
    • 数字相等运算符==和!=(§15.21.1)
    •   

  

5.1.8。拆箱转换

     

[...]

     
      
  • 如果r是Long类型的引用,则取消装箱转换会转换r   进入r.longValue()
  •   
     

[...]

     
      
  • 如果r为null,则取消装箱转换会抛出NullPointerException
  •   

请注意,由于短路评估if (value == null || value == -1)不会引发异常。由于value == nulltrue,因此永远不会评估表达式value == -1的第二部分,因此在这种情况下value不会取消装箱。

答案 3 :(得分:0)

您的问题是两次测试中检查null / primitive值的顺序。由于您传递的是null,所以:

if (value == null || value == -1) 

不会取消装箱,因为第一次检查是真的。测试从左到右进行。此

if (value == -1 || value == null)
然而,

将尝试取消装箱(以比较为-1),并且因为您要取消装箱null值而失败。该行为(取消装箱空值抛出异常)是预期的。