拆开一个空的包装对象会导致意外的NullPointerException。

6
如果您运行以下代码:
public class Foo{
    public static void main(String[] args){
        int id = new Bar().getId(); // throws unexpected NullPointerException
    }

    private static class Bar{
        private final Integer id;

        public Bar(){
            this(null);
        }

        public Bar(Integer id){
            this.id = id;
        }

        public Integer getId(){
            return id;
        }
    }
}

您将会得到以下堆栈跟踪信息:

Exception in thread "main" java.lang.NullPointerException
    at Foo.main(Foo.java:3)

为什么没有编译器警告或其他提示?在我看来,这是使用拆箱非常恶心的微妙之处,或者可能我太天真了。


补充@Javier提供的答案,如果您正在使用Eclipse,需要执行以下步骤以启用此功能:

  1. 导航到窗口 > 首选项 > Java > 编译器 > 错误/警告
  2. 展开潜在的编程问题
  3. 装箱和拆箱转换切换为"警告"或"错误"
  4. 点击"确定"

我不明白。你是在问为什么会出现NPE,还是只是在发牢骚?有什么具体可回答的问题吗? - madth3
6个回答

6
我不知道你使用的是哪个IDE,但Eclipse有一个选项可以启用对装箱和拆箱转换的警告。无法将其检测为空指针访问,因为null并非立即通过Bar.getId()进行拆箱,而是间接地发生。

类型为Integer的表达式被拆箱为int
Foo.java第3行


我已经使用Eclipse约3年了,我不知道那个!太棒了! - mre
2
很烦人的是,Eclipse不能在不装箱的情况下打开拆箱警告!https://bugs.eclipse.org/bugs/show_bug.cgi?id=163065 - Kyle

6

如果您尝试在null上使用任何方法或执行与null不相关的操作,它会抛出一个NullPointerException异常。

自动解包使用[Integer对象].intValue()方法(或类似方法)实现,因此它会抛出NullPointerException异常,因为您不能让null调用方法。

希望这可以帮助您!


5

看起来这种行为在JDK™ 5.0文档中有所记录,

...你可以很大程度上忽略intInteger之间的区别,但也有一些注意事项。一个Integer表达式可以有一个空值。如果你的程序试图自动解包空值,它会抛出一个NullPointerException


0

装箱(Boxing)不过是将对象(如Integer)强制转换为本地等效的“int”这样的语法糖。本地类型不能为null,但对象可以。在这些情况下,装箱机制无法防止NullPointerExceptions。


0

NullPointerException 是一种RuntimeException,编译器无法检测到。

因此,在进行拆箱之前最好的做法是检查空值。

int getId(){
    if(id!=null){
        return id;
    }
    // return other or throw a checked exception.
}

0

看起来是一个完全合理的运行时异常。如果你的主要代码是:

public static void main(String[] args){        
    Integer idObj = new Bar().getId();
    int id = idObj;   // throws NullPointerException
}

空指针异常并不令人意外。Bar类返回一个null,而null对象指针不能转换成一个简单的值。Bar类的实现可能会更改并将id初始化为非null值。这段代码块可以独立于Bar类进行编译,因此不应该在此代码块中编写关于Bar类动态工作方式的假设。

这可能很显然,但真正的解决方案是使用int作为id成员,而不是Integer。这样就没有问题了:

private static class Bar{
    private final int id;

    public Bar(){
        this(0);
    }

    public Bar(int id){
        this.id = id;
    }

    public int getId(){
        return id;
    }
}

(但我想你已经意识到了这一点 :-))


我并没有说这个异常是不合理的,我说它是意外的,特别是因为我的IDE默认选择忽略它。 - mre
好的,我试图绕过什么是“预期”和“非预期”的问题。这是一个运行时异常,不是可以从静态分析中确定的东西。您希望IDE在这种情况下如何表现? - AgilePro
我本来期望IDE默认至少会警告我。就像我说的,我认为这是一个相当微妙的运行时异常。 - mre
你会期望它能够确定所有可能发生运行时NPE的情况吗?这让我想起了哥德尔不完备定理。但是我建议在不需要使用Integer类时避免使用它。 - AgilePro
不,你夸张了。我不会期望它考虑到所有情况。但看起来其他人也有同样的想法,因为当涉及装箱/拆箱时,Eclipse提供了这样的功能。 - mre
1
抱歉有些夸张了,你是对的。所以你想要的警告只是它正在将一个整数内联类型转换为int吗?我猜Eclipse可以作为警告来为你完成这个。 - AgilePro

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