为什么我可以在Java中抛出null?

318

当运行这个时:

public class WhatTheShoot {

    public static void main(String args[]){
        try {
            throw null;
        } catch (Exception e){
            System.out.println(e instanceof NullPointerException);
            System.out.println(e instanceof FileNotFoundException);
        }
    }
}

响应结果为:

true  
false

这对我来说相当令人震惊。我本以为这会导致编译时错误。

在Java中,为什么我可以抛出null并将其转换为NullPointerException?

(实际上,我不知道如果我抛出null是否是“upcast”,因为我正在抛出null)

除了作为一个非常愚蠢的面试问题(请不要在面试中问这个问题),我看不到任何理由去throw null。也许你想被解雇,但那...我是说,为什么其他人会throw null 呢?

有趣的事实IntelliJ IDEA 12告诉我,我的代码e instanceof NullPointerException将永远返回false,这完全不是真的。


17
一个线索是,如果enull,则无论XYZ是什么,e instanceof XYZ都将返回false。换句话说,你没有捕获到null,而是捕获了一个实际的NullPointerException实例。至于“为什么会有人throw null”,可能是不经意地这样做(例如throw this.lastException();,其中lastException()返回null)。至于“有趣的事实” - 我想IntelliJ并不那么智能。请提交一个错误报告! - Ted Hopp
74
“...我的代码行e instanceof NullPointerException将永远为false,并不正确。” - 这句话是否有双关语的意味? - BlueRaja - Danny Pflughoeft
3
因为throw语句可以抛出引用类型的“对象”..由于对象可以为null,它允许您抛出null值。 - Anirudha
4
不是所有引用类型都可以,只有属于“Throwable”类型的引用可以,而且由于“Throwable”可以是一个空引用,所以它被允许。 - AllTooSir
2
在Java中,null有些特殊,因为它不受类型检查的限制。否则,如果你刚好没有河马,你就不能从返回Hippopotamus的方法中返回null - Hot Licks
显示剩余2条评论
7个回答

429

看起来并不是将null视为NullPointerException,而是试图throw null本身会抛出NullPointerException

换句话说,throw检查其参数是否为非空,如果参数为空,则抛出NullPointerException

JLS 14.18 规定了这种行为:

如果表达式的评估正常完成并产生一个空值,则创建并抛出类NullPointerException的实例V',而不是空值。然后throw语句突然完成,原因是使用值V'进行了抛出。


14
像Java的异常处理机制一样,没有有效的理由去throw null,但在有缺陷的程序中可能会出现这种情况。对于有缺陷的代码结构定义良好的语义可以简化调试过程,并最小化漏洞带来的安全后果。 - Mankarse
17
@bharal: 可能在编译时无法确定。在throw null的确切情况下是可以确定的,但在更一般的情况下,异常对象在运行时决定,可能无法知道。显然,为null字面量添加特殊情况(到JLS)并不被视为值得,因为这是一个非常容易避免的错误。 - Mankarse
6
补充@Mankarse的评论,你可能有一些生成异常的方法,例如 Exception generateExceptionForErrorCode( int errorCode ) 或其他类似方法,如果由于一个漏洞它返回了 null,那么你将尝试抛出 null。由于Java编译器不执行null分析,因此无法注意到这一点。 - Calum
8
“throw null”比“throw new NullPointerException()”更少的代码量,这不是一个好的理由,但这是一个理由。 - edthethird
3
请记住,你不能仅仅使用 throw new SomethingException() ,还需要使用 throw existingExceptionYouGotFromSomewhere。当你有一个复杂的异常处理程序时,它可以自己处理一些异常,但将其他异常传播到上层。在这种情况下,重新抛出异常的异常处理代码可能会存在 bug,导致异常对象为 null - Philipp
显示剩余5条评论

97

为什么会将其转换为 NullPointerException?

根据 JLS 14.18

throw 语句首先计算表达式。如果出于某种原因,表达式的计算异常终止,则 throw 终止并显示该原因。如果表达式的计算正常完成,产生非 null 值 V,则 throw 语句异常终止,原因是具有值 V 的 throw。如果表达式的计算正常完成,产生 null 值,则创建并抛出类 NullPointerException 的实例V'。然后 throw 语句异常终止,原因是具有值 V' 的 throw。

为什么可以在Java中抛出null?

您可以抛出类型为 Throwable 的对象,而由于 nullThrowable 的有效引用,编译器允许它。

这就是 Neal Gafter 所说的 (已存档)

虽然 null 可以分配给每个引用类型,但 null 的类型本身不是引用类型。我们的意图是将 throw 语句中的表达式必须是引用类型的要求从 JLS 第三版中删除,但这个更改实际上没有被发布版本采纳。因此,这是一个 javac 编译器错误,我在 SE 5 中引入了该错误。


4
这是最佳答案,因为它是唯一一个解释为什么这不是编译时错误的答案。(或者至少,是唯一一个解释不是错误的答案!) - ruakh
@ruakh,我真的很喜欢这个答案,但是被选中的答案比它更好一些,而且被选中的答案的内容基本上回答了我的问题。不过我很想知道Neal Grafter为什么认为这是一个编译错误... - bharal
5
@bharal:我认为他的评论很清楚:《Java语言规范》第三版要求throw语句中的表达式具有引用类型。null字面量没有引用类型(它只能分配给引用类型)。Gafter打算删除这个要求,并相应地修改了javac,但是这个要求并没有被删除,所以javac的行为是一个错误。 (也就是说,前述更改已经纳入了Java SE 7 版本的规范中,现在* 继续*) - ruakh
5
“[continue]”表示需要引用类型或空类型的表达式(参见http://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.18)。因此,在Java 7编译器或源代码级别设置为Java 7的编译器中,这不再是一个错误。 - ruakh

21

它遵循JLS的规定:

如果表达式的求值正常完成并产生一个null值,则会创建一个名为NullPointerException的类实例V',而不是null,并将其抛出。


18

这样考虑可以更明显地解释为什么这个方法有效:

try {
    Exception foo = null;
    if(false) {
        foo = new FileNotFoundException();
    } // Oops, forgot to set foo for the true case..
    throw foo;
} catch (Exception e){
    System.out.println(e instanceof NullPointerException);
    System.out.println(e instanceof FileNotFoundException);
}

14

不确定,但我猜 "throw null"; 不起作用,并且尝试使用它会导致程序抛出一个异常,而这个异常恰好是(鼓声)NullPointerException...


-3

bharal...这看起来像是javac编译器的一个bug。我认为它是在SE 5中引入的。 Null可以分配给任何引用类型。然而,“null的类型”本身不是一个引用类型。程序之所以能够编译它,是因为null可以简单地转换为Exception。 而且,throw在声明后查找对象引用,由于null可以作为对象引用工作,因此显示结果。

JLS文档关于throw的说明如下:

“throw语句首先评估表达式。如果表达式的评估由于某种原因突然完成,则throw也会由于该原因突然完成。如果表达式的评估正常完成,产生非空值V,则throw语句会突然完成,原因是使用值V进行抛出。如果表达式的评估正常完成,产生空值,则创建类NullPointerException的实例V',并抛出该实例而不是null。然后,throw语句突然完成,原因是使用值V'进行抛出。”


1
这是Java,不是C#。 - bmargulies
@bmargulies,你从哪里得到C#的信息?它没有被提及。 - vidstige
4
我不知道为什么我在2013年写了那个。 - bmargulies
2
可能是因为它曾经提到了"C#概念"中的"System.Exception"。 - eis

-3

null 可以转换为任何类型,包括 Exception。就像你可以返回 null 如果你的方法签名指定你应该返回一个 Exception(或者一个字符串,或者 Person 类),你也可以抛出它。

*不包括原始类型。


1
这并没有解释为什么null是NullPointerException的一个实例而不是FileNotFoundException - php_coder_3809625

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