【问题标题】:Is it okay to throw NullPointerException programmatically? [closed]以编程方式抛出 NullPointerException 可以吗? [关闭]
【发布时间】:2011-03-20 08:31:02
【问题描述】:

当有后置条件时,方法的返回值不能为null,怎么办?

我可以的

assert returnValue != null : "Not acceptable null value";

但断言可以关闭!

这样可以吗

if(returnValue==null)
      {
           throw new NullPointerException("return value is null at method AAA");
      }

?

或者在这种情况下使用用户定义的异常(如 NullReturnValueException )更好吗?

【问题讨论】:

  • 关于使用断言还是空值检查的选择:假设断言仅用于开发环境,并且断言仅用于您或其他开发的健全性检查。另一方面,空值检查适用于所有类型的客户端交互(与用户或其他人使用您的代码,如在公共 API 中),并在运行时在所有环境中运行。

标签: java exception-handling nullpointerexception throw


【解决方案1】:

我同意前面的答案中的说法,即 NPE 是代码中的错误,不应该被抛出,但开发人员应该修复意外的 null。然而,这种状态大部分时间都可以通过测试来避免。

这个问题是在 7 年前提出的,但现在我们在 java 8 中有 Optional,这个特性可以防止 NPE。

我想到的最后一个解决方案是,你应该检查对象是否为 null,如果它相等,那么抛出你自己的异常并描述发生的事情。

【讨论】:

    【解决方案2】:

    在逻辑变得如此深入以至于调用程序员很难弄清楚什么是空之前抛出 NPE 通常是一个非常好的主意。 addListener() 方法就是一个很好的例子。

    尽管存在不知情的反对意见,但 JDK 中有许多方法可以做到这一点。

    【讨论】:

      【解决方案3】:

      http://pmd.sourceforge.net/pmd-5.0.1/rules/java/strictexception.html
      “避免抛出 NullPointerExceptions。这些会令人困惑,因为大多数人会认为是虚拟机抛出了它。考虑改用 IllegalArgumentException;这将被清楚地视为程序员引发的异常。”

      【讨论】:

      • 无论如何,oracle doc 说的有些不同。 docs.oracle.com/javase/tutorial/essential/exceptions/…但我必须说我不同意以上所有文章内容。
      • 这里有很多人应该同意对任意 SourceForge 项目的任意意见吗?当实际原因就在堆栈跟踪中时,为什么“大多数人应该假设”任何事情?
      【解决方案4】:

      是的,没关系,但我认为让它发生是一个更好的决定。

      Java 程序员和 null 的问题在于,人们来自 C/C++ 背景,其中 NULL 意味着很多不同的东西。在 C/C++ 中,取消引用 NULL(或 Wild)指针是一个严重的问题,可能会导致奇怪的内存问题或使您的程序崩溃(显然是不可取的)。如果你能摆脱 C/C++ 的思维方式,并且意识到你有一个额外的层,即 JVM,它会为你处理这种情况,你就会开始对 NULL 有一点不同的看法。

      在 C++ 中,我们有引用,例如,它永远不能被赋值为 NULL。在 Java 中,没有引用,但 Java 对象参数的行为更像 C++ 指针。但是在 Java 中有很多情况,其中方法隐式应该接收参数的空值!那么,我们该怎么办?

      在 Java 中像在 C++ 中那样处理 null 的问题在于,这会导致对 null 进行检查EVERYWHERE,而在 C++ 中,您只需声明一个获取引用的方法,它明确指出它不接受 NULL。很快,每个方法都必须在其中进行健全性检查,以断言当时程序的条件,从而造成混乱。

      在方法的默认约定是 null 对参数值无效的假设下工作会更好。

      为什么?好吧,让我们看看当这样的方法接收 null 作为参数值时会发生什么。 a) 也许没关系,因为它不会取消引用该值作为其行为的一部分。在这种情况下,什么都不会发生。 b) 该值被取消引用。在这种情况下,来自 JVM 的行为正是我们想要的:抛出一个异常,表明由于参数值为 null 而违反了方法的约定,并且它包含一个堆栈跟踪,将我们一直带到以这种方式利用值的方法中的行。

      人们对 NPE 有异议,因为他们认为当您在日志中看到 NPE 时,就意味着“有人搞砸了”。但是,让我们考虑一下。与预期行为相比,NPE 作为一个糟糕的指标实际上有什么区别?我认为使用 NPE 作为预期行为的主要区别(和优势)在于它指向的不是发生它的方法,而是指向违反方法合同的调用者。这是更有用的信息。如果我们只是简单地检查 null 并抛出一个不同的异常,我们可能会误以为观察到的行为是预期的错误,而实际上调用者违反了方法的约定。恭喜,您正确地预料到调用者在调用该方法时可能会搞砸 - 但是您所做的只是让自己误入歧途,了解异常的真正原因是什么 - 或者充其量,您使用的是两个不同的异常类表示同样的事情,同时用不必要的垃圾大量弄脏代码。

      因此,归根结底,人们认为 NPE 是禁忌。从字面上看,人们不会允许它被抛出,因为它会带来一些羞耻感——就好像你不够聪明,因为你没有猜到一个值会在哪里为空。好吧,我有消息告诉你们,你们只是在编写更多无用的代码来做同样的事情。

      一些例子:

      public void foo(Object o) {
        if (o == null) {
          throw new IGotchaException("HA! You're an IDIOT! I knew it!!");
        }
        o.bar();
      }
      
      public void foo(Object o) {
        o.bar();
      }
      

      ^ 政治不同。功能上,没那么多。

      public void foo(int a, long b, double c, Object o) {
        if (o == null) {
          throw new IllegalArgumentException("Oh. Uh. Well, you've passed me null here. I... I'm not sure where to go from here because this object is kind of required for this function to do what it's supposed to do. Soooo... wouldja mind reworking your code a little bit so as to not pass null to this function as a parameter?! That'd be great thanks. Oh by the way, it's cause we deference o 40 lines below here");
        }
        // ...
        o.doSomethingWithA(a);
      }
      
      public void foo(int a, long b, double c, Object o) {
        // ...
        o.doSomethingWithA(a);
        // NullPointerException, line 40, e.g. it wasn't OK to pass null for o you lunkhead
      }
      

      ^ 也许以大量烦人的代码为代价节省了几个 CPU 周期。但是,我们在第二种情况下进行的比较较少。

      public void foo(Object a, Object b, Object c, Object d) {
        if (a == null) throw IllegalArgumentException("jackass");
        if (b == null) throw IllegalArgumentException("jackass");
        if (c == null) throw IllegalArgumentException("jackass");
        // But d is totally OK!
        // ...
        c.setSomeValueThatMayBeNull(d);
      }
      
      public void foo(Object a, Object b, Object c, Object d) {
        // ...
        c.setSomeValueThatMayBeNull(d);
        // Throws null pointer exception if c is null, but not if d is null. Which is basically the same as above
      }
      

      ^ 声明中隐含了契约,而不是方法开头的异常情况。没有其他缺点。

      public void foo(Object o) {
        if (o == null) {
          doTheBoogie();
        } else {
          doTheRobot();
        }
      }
      

      ^ 不好

      public void foo(Object o, int b) {
        Bar value = o.findSomethingWhichMayExist(b);
        if (value == null)
          return;
        value.doSomething();
      }
      

      ^ 使用空返回值来表示没有值。好的。

      人们遇到 NPE 问题的另一个原因是他们不知道如何处理异常。 NPE 永远不应该成为阻碍者。适当的行为是捕获 RuntimeException,大概是在调用堆栈中更高(或更低,取决于您如何看待它)的级别,该级别捕获它并在“main”之前报告它。也就是说,假设您正在开发的程序需要更具弹性并且不能在 NPE 发生时崩溃

      底线:永远不要期望为方法参数传递空值是有效的。并且绝对不要为明确接受 null 并将其视为有效值的方法创建合同。但是,允许空指针异常发生,自然而然地让代码失败,或者在无关紧要的时候不失败。

      【讨论】:

      • 如果结果是一个比尽早抛出 NPE 更模糊的问题,那么“让它发生”并不是一个更好的决定。例如,考虑添加Listener:检查 null 比让侦听器调用线程稍后遇到 NPE 并打印距问题现场数英里远的 osbscure strack 跟踪更为合适。
      • EJP 在这种情况下我会抛出 java.lang.IllegalArgumentException 因为创建侦听器的标准不为空。
      【解决方案5】:

      当有后置条件时,方法的返回值不能为null,怎么办?

      后置条件意味着如果不满足条件,则相关方法存在错误。在代码中表达这一点的方法是在后置条件上使用assert。直接抛出异常,例如 NullPointerExceptionIllegalStateException,会有点误导,因此会被误导。

      可以通过编程方式抛出 NullPointerException 吗?

      NPE 的 Java API 文档说“是”,但是,从该页面上给出的投票来看,3:1 的大多数开发人员说“不”。所以我想说这取决于您工作组中的约定。

      API 文档首先列出了 JVM 引发 NPE 的情况,因为代码尝试对需要某种对象(如调用方法或访问字段)的空引用调用操作,而 null 是不是一个对象。然后声明:

      应用程序应抛出此类的实例以指示 null 对象的其他非法用途。

      有趣的是,null 在这里被称为»object«,但它不是。这让我想起了 NullPointerException 这个名字对于没有指针的语言来说是很奇怪的。 (在 Microsoft .NET 类库中应该是 NullReferenceException。)

      那么,我们是否应该根据这一点忽略 API 文档?我不这么认为。类库确实使用文档中描述的 NPE,例如 java.nio.channels:

      除非另有说明,否则将null 参数传递给此包中任何类或接口中的构造函数或方法将导致NullPointerException 被抛出。

      这不是 JVM 生成的 NPE,而是带有附加错误消息的编码 NPE,说明哪个参数是 null(如 "in" is null!)。 (代码可以通过javap -c -p java.nio.channels.Channels | more查看,寻找private static void checkNotNull。)还有很多类以这种方式使用NPE,本质上是IllegalArgumentException的特例。

      所以在对此进行了一些调查和思考之后,我发现这是对 NPE 的一个很好的使用,因此我同意 API 文档和少数 Java 开发人员(根据本页上的投票)的观点您有权并有权以与 Java 类库相同的方式在自己的代码中使用 NPE,即提供错误消息,这在 JVM 生成的 NPE 中明显缺失,这就是为什么告诉两者没有问题各种 NPE 分开。

      为了解决 NPE 无论如何都会被抛出的小问题:尽早发现错误而不是允许 JVM 继续执行程序是非常有意义的,可能涉及磁盘或网络 I/O (和延迟),并生成不必要的大堆栈跟踪。

      【讨论】:

        【解决方案6】:

        NullPointerException 的问题在于,当您忘记检查某些内容是否为 null 或给出不应该为 null 的错误参数时,就会发生这种情况。

        根据我的经验,Java 程序员很快就知道这个异常是由代码中的错误引起的,所以手动抛出它会让 大多数 的人非常困惑。 IllegalArgumentException 是更好的主意,当您传递不可接受的参数时(例如 null,其中某些内容不得为 null)。

        它还触发了另一种启发式方法。 NPE = 有人在此处的代码中出错,IllegalArgumentException = 赋予该方法的对象无效。

        另一方面,javadoc 告诉我们:

        应用程序应抛出此类的实例以指示
        null 对象的其他非法用途。

        所以抛出 NPE 是合法的,但这不是常见的做法,所以我推荐IllegalArgumentException

        【讨论】:

        • 我认为这是不正确的。确实,NullPointerException 表示我忘记检查某些内容。但是当它被方法直接抛出时,如果禁止传入null,则实际上存在该错误。所以唯一的好办法就是扔NPE。
        • 问题是,如果你按照这个问题和JDK中无数的例子故意抛出它,你的第一句话是不正确的。
        • 当您处于值为 null 并由官方 java 文档推荐的情况下,抛出 NPE 很好,请检查以下链接。 docs.oracle.com/javase/6/docs/api/java/lang/…
        • 如果我作为开发人员遇到了异常(没有被捕获),我通常会更多地查看异常消息而不是异常的名称。我会使用Objects.requireNonNull,它会抛出 NPE,但请确保包含一条消息,以便清楚地看到什么是空的。
        【解决方案7】:

        绝对是的。

        即使是 JDK7 也能解决这个问题。见Objects#requireNonNull

        void doWith(final Object mustBeNotNull) {
        
            /*
            // bush style
            if (mustBeNotNull == null) {
                throw new IllegalArgumentException("mustBeNotNull must not be null");
            }
            */
        
            /*
            // obama style
            if (mustBeNotNull == null) {
                throw new NullPointerException("mustBeNotNull must not be null");
            }
            */
        
            // kangnam style
            Objects.requireNonNull(mustBeNotNull, "mustBeNotNull must not be null");
        
            assert mustBeNotNull != null;
        }
        

        【讨论】:

          【解决方案8】:

          正如 Joshua Bloch 曾经说过的那样:“Null 糟透了!” :) 每当我的脸是 null 时,我都会尝试使用 guava 提供的 Optional。对我来说优势很多。

          【讨论】:

          • 好奇他在哪里说的
          • @Kirby 或者 if 他说了,或者如果它不同意官方文档,为什么会有不同。
          【解决方案9】:

          我建议你永远不要自己扔NullPointerException

          正如 Thorbjørn Ravn Andersen 在下面的评论中所说,不这样做的主要原因是您不想将“真实、糟糕的 NPE”与故意抛出的 NPE 混为一谈。

          因此,在您确信自己能够识别“有效”NPE 之前,如果您想告诉 API 用户 null 不是有效的参数值,我建议您使用 IllegalArgumentException .应该记录您的方法在传递非法空参数时的行为。

          另一个(更现代的恕我直言)选项是在参数附近使用@NotNull 注释。 这里是an article about using @NotNull annotation

          正如我之前提到的,在某些情况下,抛出 NPE 不会让你或你的队友感到困惑:NPE 的原因应该是清晰和可识别的。

          例如,如果您使用一些带有前置条件模块的库,例如Guava,那么我发现使用类似checkNotNull() 的方法是处理非法传递的空值的更好方法。

          checkNotNull(arg, msg) 抛出 NPE,但从堆栈跟踪中很明显,它是由 Preconditions.checkNotNull() 生成的,因此这不是未知错误,而是预期行为。

          【讨论】:

          • 实际上 NullPointerException 是一种很棒的方式来告诉你,一个参数不应该为空。请参阅 Effective Java:可以说,所有错误的方法调用都归结为非法参数或非法状态,但其他异常通常用于某些类型的非法参数和状态。如果调用者在某个参数中传递了 null 值,而该参数被禁止为 null 值,则约定会抛出 NullPointerException 而不是 IllegalArgumentException。
          • 看看javadoc:应用程序应该抛出这个类的实例来指示空对象的其他非法使用。这对我来说很清楚。抛出 NPE 是合法有效的,应该在传入 null 时对每个非 null 参数进行。期间。
          • @willi,保持 npe 仅由运行时而不是您自己的代码抛出,这样可以轻松确定 npe 的严重程度。
          • 我同意威士忌。 NPE 是告诉某个方法参数为空的方法,并且是它的完美例外。不是 IllegalArgumentException。同样将它扔到您自己的代码中与 Java 库代码中的 NPE 没有什么不同。我按合同进行编码,因此我的方法的用户在阅读我的方法的 Javadoc 时会得到他应该期望的结果。抛出它会阻止代码进一步执行(提前失败);否则无论如何它都会在与实际错误所在的位置完全无关的地方失败。应始终认真对待 NPE,因为它表明存在编程错误。
          • 最终这是关于 softwareengineering.SE 的讨论,但是关于使用代码作为 模型 行为的方式,尽管有文档,但抛出 NPE 并没有帮助。从经验丰富的计算机科学家C. A. R. Hoare 那里得到它,他称空引用的纯粹发明是他的“数十亿美元的错误”。比抛出 NPE 更好的是通知调用者输入无效的原因。说输入为空是,但说参数无效是更好,因为空不是一个有效的选项。
          【解决方案10】:

          我认为在 JVM 为您执行 NPE 之前尽早抛出 NPE 没有问题 - 特别是对于空参数。关于这一点似乎存在一些争论,但 Java SE 库中有很多例子就是这样做的。我不明白为什么NPE在你不能自己扔的方面是神圣的。

          但是,我离题了。这个问题是关于不同的东西。您正在谈论一个说明返回值不能为空的后置条件。在这种情况下肯定为 null 意味着您有一个错误方法内部

          您甚至会如何记录这一点? “如果返回值意外为空,此方法会抛出 NullPointerException”?不解释这是怎么发生的?不,我会在这里使用断言。异常应该用于可能发生的错误——而不是覆盖如果方法内部有问题可能发生的事情,因为这对任何人都没有帮助。

          【讨论】:

          • 在空参数的情况下,IllegalArgumentException 可能是更合适的抛出异常。 NullPointerException 表示尝试对空值执行某些操作。如果 NPE 是您的目标,那么只需在您的方法中放弃 null 检查,NPE 将自然地被 JVM 抛出。
          【解决方案11】:

          在 Java-verse 中,当期望一个对象时,null 始终是一个有效值。你最好避免这种不可能的后置条件。如果你真的不能忍受 null 那么你将不得不重新设计你的方法,这样你就可以返回一个原语。

          【讨论】:

            【解决方案12】:

            如果你描述了一个返回值不能是null的方法契约,那么你最好确保你不返回null。但这根本不是 NullPointerException。如果您必须返回的值是null,那么显然调用者给了您错误的参数(IllegalArgumentException),您没有处于有效状态(IllegalStateException),或者发生了一些其他更有意义的异常情况除了 NullPointerException(通常表示编程错误)。

            【讨论】:

              【解决方案13】:

              在某些情况下抛出该异常不是一个好习惯,我想知道为什么当您已经使用 if 语句捕获它时?

              如果(returnValue==null)

              【讨论】:

                【解决方案14】:

                IMO 你永远不应该手动抛出 NullPointerException。如果不检查描述,调用例程将不知道是真实的还是手动的 NullPointerException。在这种情况下,您似乎希望将自己的异常与问题更接近,以便调用方法可以正确地从该异常中恢复。也许 PostConditionException 在许多情况下都足够通用。

                【讨论】:

                • 完全同意,应该是JVM抛出的。我们应该抓住它并处理它。
                • 我完全不同意。在不期望它的上下文中的空值应该产生 NPE。自己扔的好处是可以在里面加一条有用的信息。
                【解决方案15】:

                我认为 NullPointerException 的用法没问题,如果你记得描述。这就是调查人员所使用的(行号可能会发生变化)。还要记住记录您的方法在特殊情况下会抛出空指针异常。

                如果您一开始就检查您的方法参数,我也可以接受throw new IllegalArgumentException("foo==null")

                【讨论】:

                  【解决方案16】:

                  当然没有一个普遍的规律来反对抛出 NullPointerException,但是如果你真的应该在这样一个抽象的例子中回答,那就很难回答了。您不想做的是将人们置于试图捕获 NullPointerException 的位置。像这样的代码(真实的例子,我发誓):

                  catch (NullPointerException npe) {
                    if (npe.getMessage().equals("Null return value from getProdByCode") {
                      drawToUser("Unable to find a product for the product type code you entered");
                    } 
                  }
                  

                  这是一个万无一失的指标,你做错了什么。因此,如果 null 返回值是您实际能够传达的某些系统状态的指示符,请使用传达该状态的异常。没有多少情况我能想到对引用进行空值检查以丢弃空指针是有意义的。通常,下一行代码无论如何都会丢弃空指针(或更多信息)!

                  【讨论】:

                  • 这个特殊的例子可能是由空的 ProdCode 输入字段引起的?
                  • 如果只是,那至少有点道理。这就是与我合作的合同开发人员根据用户输入处理搜索结果的方式。
                  【解决方案17】:

                  JavaDoc for NullPointerException 声明:

                  当应用程序尝试 在对象是的情况下使用 null 必需的。其中包括:

                  * Calling the instance method of a null object.
                  * Accessing or modifying the field of a null object.
                  * Taking the length of null as if it were an array.
                  * Accessing or modifying the slots of null as if it were an array.
                  * Throwing null as if it were a Throwable value. 
                  

                  应用程序应该抛出的实例 这个类来指示其他非法的 使用空对象。

                  我认为违反后置条件是非法行为。但是,我认为您使用什么异常并不重要,因为我们正在讨论一个应该(并且希望是)无法访问的代码路径,因此您不会有特定于该异常的错误处理,因此唯一的效果该名称的名称是日志文件中某些条目的不同措辞。

                  相反,如果您认为后置条件可能会被违反,最好包含更多调试信息,例如调用方法时使用的参数。

                  【讨论】:

                    【解决方案18】:

                    我称之为O'Reilly's Java in A Nutshell,这本书是由一位专家编写的,它列出了 NullPointerException 的这个定义:

                    表示尝试访问字段或调用空对象的方法。

                    由于返回 null 不是其中任何一个,我认为编写自己的异常会更合适。

                    【讨论】:

                    • Erm,仅仅因为一些未命名的书使用了这个定义并不能使它成为一个普遍接受的定义 - 特别是如果它与那个例外的 JavaDoc 相矛盾。
                    • 来自 java 文档,“当程序尝试访问对象的字段或方法或数组的元素时,如果没有实例或数组可供使用,即如果对象或数组指向 {@code null}。它也出现在其他一些不太明显的情况下,例如 Throwable 引用为 {@code null} 的 {@code throw e} 语句。"
                    • Java 文档在哪里...?该异常的 JavaDoc 以“应用程序应抛出此类的实例以指示空对象的其他非法使用”结尾。
                    • 您在看什么 JavaDoc? docjar.com/docs/api/java/lang/NullPointerException.html 从 JavaDoc 获取信息。您误解了您从称为 JavaDoc 的网站获得的信息,这实际上更像是 wiki。这些情况都与返回空值无关。
                    • 有趣的是,Apache Harmony 项目会选择提供与我链接到的 JDK 的 Sun 实现不同的 javadoc(该链接不再托管在 sun 上,这确实是 Oracle 的错)。请注意,根据他们的网站,Apache Harmony 不是经过认证的 Java 实现。事实上,他们并没有声称与它完全兼容。
                    【解决方案19】:

                    鉴于 NullPointerException 是在 Java 中传达意外空值的惯用方式,我建议您使用标准的 NullPointerException 而不是自己开发的。另请记住,最小意外原则建议您不要为存在系统异常类型的情况发明自己的异常类型。

                    断言有利于调试,但如果您必须处理某些条件则不好,因此这并不是处理错误条件的好方法。

                    【讨论】:

                      猜你喜欢
                      • 2011-02-09
                      • 2011-11-24
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2013-04-03
                      相关资源
                      最近更新 更多