【问题标题】:Throw an exception or return null抛出异常或返回 null
【发布时间】:2012-06-08 01:33:01
【问题描述】:

如果我有下面的功能,有两种选择

private MyObject findBlank() {
    for (int i = 0; i < pieces.length; i++) {
        if(pieces[i].isBlank()){
            return pieces[i];
        }
    }
    return null;
}

private MyObject findBlank() {
    for (int i = 0; i < pieces.length; i++) {
        if(pieces[i].isBlank()){
            return pieces[i];
        }
    }
    throw new NoSuchFieldError("No blank piece found!");
}

从这个方法中我知道它应该总是返回一个对象“片段”之一总是isBlank() == true,最后返回 null 只是为了取悦编译器。既然是这种情况,我的代码如果返回 null 无论如何都不会工作,那么抛出异常是否正确?

我的选择是:

  1. 返回 null 并且应用程序在某些极端情况下会收到 NullPointerException
  2. 返回 null 并使用 (myObject != null) 检查包装该方法的使用
  3. 抛出会在运行时将其炸毁的异常

我想我要问的是,这是抛出异常的正确位置吗?即,如果它陷入这种情况,我无能为力。这是归类为“异常”还是我应该检查我的方法返回的内容(这使我的代码看起来很糟糕)。如果我知道它不应该返回 null 那么我应该抛出异常对吗?

另外我将如何选择什么异常,或者扩展一个并抛出我自己的?

【问题讨论】:

  • “两者都没有”的建议没有吸引力吗? :-)
  • @missingfaktor 我在 Android 中编码,所以无法引入其他重型框架
  • 明白。对于它的价值,这个特定的抽象并不是那么繁重,如果你觉得它有用,可以添加only the required part。
  • 好吧,我相信抛出异常是最好的选择。你永远不应该返回 null 。相反,您可以做的是在 try-catch 块中调用此方法,并确保在抛出异常时在 catch 块中执行某些操作。就最佳实践而言,您还可以让调用此方法的父类向上游传递异常,如果需要,可以在 UI 级别显示。

标签: java exception coding-style null


【解决方案1】:

我相信正确答案取决于调用该方法的代码。有两种情况:

  1. 调用代码不确定对象是否存在,并且将有代码专门处理它不存在的情况。

  2. 调用代码确信该对象存在,如果它不存在,逻辑上会存在其他一些深层问题,在这种情况下您所能做的就是放弃并报告错误。

在实践中,我经常使用命名约定来区分它们:

private MyObject findBlankOrNull() {
    for (int i = 0; i < pieces.length; i++) {
        if(pieces[i].isBlank()){
            return pieces[i];
        }
    }
    return null;
}

private MyObject findBlankOrFail() throws Exception {
    MyObject obj = findBlankOrNull();
    if (obj != null) {
        return obj;
    }
    throw new NoSuchFieldError("No blank piece found!");
}

请注意,OnFail 版本总是可以调用另一个版本,然后抛出异常而不是返回 null。本质上,您所做的是插入 Exception throw 代替 return null 以保证不返回 null,因此您不需要任何代码来在调用站点测试 null。

我写了一篇关于这个主题的博文:Return Null or Exception?,其中更详细地介绍了它。

【讨论】:

    【解决方案2】:

    return null 大部分时间都会从合约视图中丢失信息,如果从生产者那里获取 null,消费者无法知道错误响应的原因是什么。

    查看您的第一个代码,外部代码有两种情况得到 NULLPointerException: 1. 件数为空 2. 作品没有这样的元素

    所以 return null 会误导外部代码进行进一步的操作,它会引起潜在的问题。

    说说return nullObject(not null)和exception的区别,主要区别是PROBABILITY,意思是: 1.如果空情况的可能性更大,它应该返回 nullObject 以便所有外部代码都可以/应该明确地处理它们。 2.如果空的情况概率较小,为什么不抛出异常,让final call函数直接处理。

    【讨论】:

      【解决方案3】:

      除了大多数答案之外,我想指出的是,如果您关心的是 性能,那么 异常比返回 null 慢

      看看这段代码:

      class Main {
          public static void main(String[] args) {
              testException();
              testNull();
          }
      
          public static void testException() {
              long st = System.currentTimeMillis();
              for(int i=0;i<10000000;i++) {
                  try{
                      exp();
                  } catch(Exception e) {
      
                  }
              }
              long et = System.currentTimeMillis();
              System.out.println("Time taken with exceptions : "+(et-st));
          }
      
          public static void testNull() {
              long st = System.currentTimeMillis();
              for(int i=0;i<10000000;i++) {
                  returnNull();
              }
              long et = System.currentTimeMillis();
              System.out.println("Time taken with null : "+(et-st));
          }
      
          public static void exp() throws Exception {
              throw new Exception();
          }
      
          public static Object returnNull() {
              return null;
          }
      }
      

      我机器上的结果是:

      Time taken with exceptions : 7526
      Time taken with exceptions : 5
      

      如果抛出异常在您的代码中是一种罕见的情况并且不会经常发生,那么这两种情况所花费的时间几乎相同。

      您必须在性能与可维护性/可读性之间进行权衡。

      阅读更多关于它的信息here

      【讨论】:

        【解决方案4】:

        使用Null Object pattern 可能是个好主意。

        Provide an object as a surrogate for the lack of an object of a given type. The Null Object provides intelligent do nothing behavior, hiding the details from its collaborators

        因此,在这种情况下,您不必使用异常或返回 null。您始终可以返回预期的返回类型对象。诀窍是当您没有要返回的内容时,您可以返回与预期返回类型相同的 Null object,而不是返回 null 或抛出异常。

        这个documentation 有一些例子和描述。和你有类似的情况,通过设计模式解决。

        public class CustomerFactory {
        
          public static final String[] names = {"Rob", "Joe", "Julie"};
        
          public static AbstractCustomer getCustomer(String name){   
            for (int i = 0; i < names.length; i++) {
               if (names[i].equalsIgnoreCase(name)){
                 return new RealCustomer(name);
               }
            }
            return new NullCustomer();
          }
        }
        

        【讨论】:

          【解决方案5】:

          是的,您应该抛出一个RuntimeException 来表示不应该发生的“异常”情况。 IllegalStateException 可能符合要求。确保包含一条消息,其中包含任何有助于您找到错误(如果它被抛出)的信息。

          【讨论】:

          • 我不确定我是否同意“无法恢复”部分。
          • @HovercraftFullOfEels - 你建议它应该如何措辞?
          • 我会删除“您的程序无法从中恢复”部分。
          【解决方案6】:

          我建议使用我刚才在another answer 中谈到的Maybe(也称为Option)数据类型。

          Functional Java 中的此数据类型 is available。

          用法:

          private Option<MyObject> findBlank() {
              for (int i = 0; i < pieces.length; i++) {
                  if(pieces[i].isBlank()){
                      return Option.some(pieces[i]);
                  }
              }
              return Option.none();
          }
          

          旁注:

          您的findBack 方法可以推广到以谓词作为参数的方法,并找到并返回满足它的第一个元素。

          不出所料,Functional Java already has that as well。

          让我们暂时假设pieces 是fj.data.List。那么你的方法可以改写为:

          private Option<MyObject> findBlank() {
            return pieces.find(new F1<MyObject, Boolean>() {
              public Boolean f(MyObject p) {
                return p.isBlank();
              }
            });
          }
          

          另一个旁注:

          也许上面的代码看起来很粗糙。 IntelliJ IDEA's "closure folding" can be of some help here.

          【讨论】:

            【解决方案7】:

            关于你的选择,问问自己是否

            1. 在此方法返回意外值(即null)后,让您的程序在某个时候崩溃是个好主意吗?
            2. 如果屏蔽null 返回值,究竟会隐藏什么?
            3. 仅仅因为值错误就立即炸毁是个好主意吗?

            我个人会选择选项 2 或 3,这取决于我更喜欢​​问题 2 还是问题 3 的答案。选项 1 绝对是一个坏主意,尤其是在它不应该发生的情况下。如果程序在您的函数返回后抛出 NPE 方式,您将很难弄清楚 null 的来源。特别是如果它发生在您完成此特定功能的几个月后。

            如果您选择抛出异常,您会立即看到哪里出了问题,您可以直接去那里找出为什么出了问题。返回null 并在调用函数中检查它也可以工作,但前提是您不会静默失败,而是实际采取一些措施来正确处理问题。

            【讨论】:

              【解决方案8】:

              如果你的数组应该总是有一个有效的值要返回,你应该引发一个异常作为后备。 (在您的示例中为 2sd 案例)

              最终,您可以声明自己的异常种类(类)。

              【讨论】:

                【解决方案9】:

                应该总是返回一个对象或返回null

                在某些极端情况下,应用程序将获得 NullPointerException
                这两者是矛盾的。

                如果你真的确定你总是有pieces[i].isBlank() 然后抛出IllegalStateException

                否则按您的要求处理。

                【讨论】:

                  【解决方案10】:

                  我想我要问的是,这是抛出异常的正确位置吗?

                  如果是异常情况,那么是的。如果预期可能找不到任何符合条件的内容,则情况并非异常,您应该返回null。

                  【讨论】:

                    猜你喜欢
                    • 2012-12-22
                    • 1970-01-01
                    • 2018-07-27
                    • 1970-01-01
                    • 2016-11-09
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2014-09-07
                    相关资源
                    最近更新 更多