Boolean.valueOf() 有时会产生空指针异常

116

我有这段代码:

package tests;

import java.util.Hashtable;

public class Tests {

    public static void main(String[] args) {

        Hashtable<String, Boolean> modifiedItems = new Hashtable<String, Boolean>();

        System.out.println("TEST 1");
        System.out.println(modifiedItems.get("item1")); // Prints null
        System.out.println("TEST 2");
        System.out.println(modifiedItems.get("item1") == null); // Prints true
        System.out.println("TEST 3");
        System.out.println(Boolean.valueOf(null)); // Prints false
        System.out.println("TEST 4");
        System.out.println(Boolean.valueOf(modifiedItems.get("item1"))); // Produces NullPointerException
        System.out.println("FINISHED!"); // Never executed
    }
}
我的问题是我不明白为什么测试3能够正常工作(它打印false并且不会产生NullPointerException),而测试4会抛出NullPointerException。正如您在测试12中所看到的,nullmodifiedItems.get("item1")是相等的,都是null
这种行为在Java 7和8中都是一样的。

modifiedItems.get("item1") 这是 null,你已经意识到了,但你假设将其传递给 valueOf 不会导致 NPE? - Stultuske
16
“@Stultuske提出了一个合理的问题,因为在函数中传递字面量“null”不会生成NPE,而在两行代码之前却会。这背后有一个很好的原因,但对于初次看到的人来说肯定会感到困惑 :) ” - psmears
25
我印象深刻。这是我多年来看过最有趣的空指针异常问题。 - candied_orange
@Jeroen 这不是 那个问题 的复制。虽然拆箱在这两个问题中都很常见,但这里并没有进行任何比较。这个问题的关键是它发生的原因与重载解析方式有关;而这与 == 的应用方式完全不同。 - Andy Turner
5个回答

181

你需要仔细查看调用的重载方法:

  • Boolean.valueOf(null) 调用的是 Boolean.valueOf(String)。即使提供了一个空参数,也不会抛出NPE
  • Boolean.valueOf(modifiedItems.get("item1")) 调用的是Boolean.valueOf(boolean),因为 modifiedItems 的值的类型为 Boolean,需要进行拆箱转换。由于 modifiedItems.get("item1")null,它是该值的拆箱 - 而不是 Boolean.valueOf(...) - 导致了 NPE。

确定调用哪个重载方法的规则相当复杂,但大致如下:

  • 在第一次搜索方法匹配时,不允许装箱 / 拆箱(也不允许变长参数方法)。

    • 由于nullString的可接受值,但不是boolean的可接受值,因此Boolean.valueOf(null)在此次搜索中匹配到了Boolean.valueOf(String)
    • Boolean既不是Boolean.valueOf(String)的可接受类型,也不是Boolean.valueOf(boolean)的可接受类型,因此Boolean.valueOf(modifiedItems.get("item1"))在此次搜索中没有匹配到任何方法。
  • 在第二次搜索方法匹配时,允许装箱 / 拆箱(但仍不允许变长参数方法)。

    • 一个 Boolean 可以拆箱为 boolean,因此在这个步骤中,Boolean.valueOf(boolean)Boolean.valueOf(modifiedItems.get("item1")) 匹配;但编译器必须插入一个拆箱转换来调用它:Boolean.valueOf(modifiedItems.get("item1").booleanValue())
  • (还有第三步允许可变元方法,但这对本文不相关,因为前两步匹配了这些情况)


3
在源代码中,如果我们使用Boolean.valueOf(modifiedItems.get("item1").booleanValue())而不是Boolean.valueOf(modifiedItems.get("item1")),那么代码会更清晰吗? - CausingUnderflowsEverywhere
1
@CausingUnderflowsEverywhere 不是真的 - 很难看到表达式中嵌入的.booleanValue()。两个观察结果:1)自动装箱/拆箱是Java的一个有意的特性,可以消除语法上的冗余;自己做也是可能的,但不是惯用法;2)这对你没有任何帮助 - 它肯定不能阻止问题发生,当失败发生时也不会提供任何额外信息(堆栈跟踪将是相同的,因为执行的代码是相同的)。 - Andy Turner
@CausingUnderflowsEverywhere 最好使用工具来突出问题,例如IntelliJ会提示您这里可能存在空指针异常。 - Andy Turner

13

由于modifiedItems.get返回一个Boolean(它不可转换为String),所以应使用的签名是Boolean.valueOf(boolean),其中Boolean被拆箱为原始的boolean。一旦在那里返回null,拆箱将失败并引发NullPointerException


11

方法签名

方法Boolean.valueOf(...)有两个签名:

  1. public static Boolean valueOf(boolean b)
  2. public static Boolean valueOf(String s)

您的modifiedItems值为Boolean类型。您不能将Boolean类型转换为String类型,因此将选择第一个签名。

布尔值拆箱

在您的语句中

Boolean.valueOf(modifiedItems.get("item1"))

可以理解为

Boolean.valueOf(modifiedItems.get("item1").booleanValue())   

然而,modifiedItems.get("item1") 返回 null,所以你基本上会有

null.booleanValue()

这显然会导致一个NullPointerException


措辞不当,感谢您的指正,根据您的反馈更新了答案。抱歉,在撰写时我没有看到您的答案,我发现我的答案看起来像是您的。为避免给OP带来困惑,我应该删除我的答案吗? - Al-un
4
不要因为我的原因删除它。记住,这不是一个零和游戏:人们可以(而且确实)给多个答案点赞。 - Andy Turner

3

正如Andy已经非常好地解释了 NullPointerException 的原因:

这是由于布尔解包造成的:

Boolean.valueOf(modifiedItems.get("item1"))

转换为:

Boolean.valueOf(modifiedItems.get("item1").booleanValue())

如果modifiedItems.get("item1")为空,在运行时会抛出NullPointerException异常。

现在我想在这里补充一点,以下类的自动拆箱到它们各自的基本类型也可能会产生NullPointerException异常,如果它们对应的返回对象为null。

  1. byte - Byte
  2. char - Character
  3. float - Float
  4. int - Integer
  5. long - Long
  6. short - Short
  7. double - Double

以下是代码:

    Hashtable<String, Boolean> modifiedItems1 = new Hashtable<String, Boolean>();
    System.out.println(Boolean.valueOf(modifiedItems1.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Byte> modifiedItems2 = new Hashtable<String, Byte>();
    System.out.println(Byte.valueOf(modifiedItems2.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Character> modifiedItems3 = new Hashtable<String, Character>();
    System.out.println(Character.valueOf(modifiedItems3.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Float> modifiedItems4 = new Hashtable<String, Float>();
    System.out.println(Float.valueOf(modifiedItems4.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Integer> modifiedItems5 = new Hashtable<String, Integer>();
    System.out.println(Integer.valueOf(modifiedItems5.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Long> modifiedItems6 = new Hashtable<String, Long>();
    System.out.println(Long.valueOf(modifiedItems6.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Short> modifiedItems7 = new Hashtable<String, Short>();
    System.out.println(Short.valueOf(modifiedItems7.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Double> modifiedItems8 = new Hashtable<String, Double>();
    System.out.println(Double.valueOf(modifiedItems8.get("item1")));//Exception in thread "main" java.lang.NullPointerException

1
在运行时被转换成...,实际上是在编译时被转换成那个。 - Andy Turner

0
一种理解方式是,当调用Boolean.valueOf(null)时,Java被明确告知要评估null。
然而,当调用Boolean.valueOf(modifiedItems.get("item1"))时,Java被告知从对象类型为Boolean的HashTable中获取值,但它没有找到类型为Boolean的对象,而是找到了一个死路(null),尽管它期望的是Boolean。NullPointerException异常被抛出,因为Java的创建者决定这种情况是程序出现问题需要程序员注意的实例。(发生了意外的事情。)
在这种情况下,更多的是有意地声明您打算将null放在那里,而不是Java找到缺少对象引用(null)的情况,而本应找到对象。
有关NullPointerException的更多信息,请参见此答案: https://dev59.com/u4Pba4cB1Zd3GeqPozCC#25721181

如果有人能帮助改进这个答案,我想到了一个词,它指的是程序员以清晰的意图、没有歧义地编写代码。 - CausingUnderflowsEverywhere

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接