抛出异常还是返回null?

6
如果我有以下函数,有两个选择:
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检查(这会使我的代码看起来很糟糕)。如果我知道它不应该返回null,那么我应该抛出异常,对吧?
此外,我该如何选择异常,或者扩展一个异常并抛出自己的异常?

选择异常时的帮助:http://wuhrr.wordpress.com/2007/11/22/java-exceptions-list/ - Blundell
“neither”这个建议不吸引人吗? :-) - missingfaktor
@missingfaktor 我正在编写安卓应用,所以不能引入其他沉重的框架。 - Blundell
明白了。就这个抽象来说,并不是很复杂,如果你觉得有用的话,你可以添加仅需的部分 - missingfaktor
我认为抛出异常是最好的做法。你永远不应该返回null。相反,你可以在try-catch块中调用此方法,并确保在抛出异常时在catch块中执行某些操作。就最佳实践而言,你还可以让父类调用此方法将异常传递到上游,如果需要,可以在UI级别显示异常。 - Radioactive
10个回答

12

我的问题是:这里抛出异常是否正确?

如果这是一个非常规情况,那么是的。如果找不到任何与条件匹配的内容是“预料之中”的,那么这种情况就不是“异常情况”,应该返回null


4

是的,你应该抛出一个RuntimeException来表示一种不应该发生的“异常”情况。IllegalStateException可能适合这种情况。确保在任何时候抛出异常时都包含一条带有任何有助于查找bug的信息的消息。


我不确定我同意“不能恢复”的部分。 - Hovercraft Full Of Eels
@HovercraftFullOfEels - 你建议应该怎么措辞? - Paul Bellora
1
我会删除“从中您的程序无法恢复”的部分。 - Hovercraft Full Of Eels

4

关于你的选择,问问自己:

  1. 当该方法返回意外值(例如null)后,程序崩溃是个好主意吗?
  2. 如果屏蔽null返回值,会隐藏什么?
  3. 因为一个错误的值就立即崩溃,这是个好主意吗?

个人认为应该选择选项2或3,具体取决于你更喜欢问题2或3的答案。选项1绝对不是个好主意,特别是如果不应该发生这种情况。如果程序在函数返回后很久才抛出NPE,那么你将很难找到null来源,特别是如果几个月后才发现此问题。

如果你选择抛出异常,你可以立即看到哪里出了问题,并直接去那里找出为什么会出错。在调用函数中返回null并检查它也可以工作,但只有当你不会默默失败,而是做一些正确处理问题的事情时才行。


2
除了大多数答案外,我想指出,如果您关注 性能,那么异常比返回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

如果在您的代码中,抛出异常是一种罕见情况,并且不会经常发生,那么在这两种情况下所花费的时间几乎相同。
您需要在性能与可维护性/可读性之间进行权衡。
这里阅读更多相关信息。

1

也许使用空对象模式是个好主意。

提供一个对象作为给定类型对象的缺失的替代品。空对象提供智能无操作行为,将其细节隐藏在协作者中。

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

文档中有一些示例和说明。并且有一个类似的情况,通过设计模式解决了问题。

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();
  }
}

1

大多数情况下返回null会导致合同视图中的信息丢失,如果生产者返回null,则消费者无法知道错误响应的原因。

查看您的第一段代码,外部代码会出现两种情况的NULLPointerException: 1. pieces为null 2. pieces没有这样的元素

因此,返回null将误导外部代码进行进一步操作,这将带来潜在问题。

关于返回nullObject(而不是null)和异常之间的区别,主要区别在于PROBABILITY,即: 1. 如果空情况更有可能发生,则应返回nullObject,以便所有外部代码都可以/应该明确处理它们。 2. 如果空情况的概率较小,为什么不抛出异常,以便最终调用函数可以直接处理它。


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来构造。本质上,您所做的是在返回null的位置插入异常抛出,以保证不返回null,并且不需要在调用站点编写任何测试null的代码。
我在博客中发表了一篇关于这个主题的文章: 返回Null还是异常?,其中详细介绍了它。

0

应该始终返回一个对象或者返回null

在某些边缘情况下,应用程序将会得到一个NullPointerException
这两个是矛盾的。

如果你确信总是有 pieces[i].isBlank(),那么就抛出 IllegalStateException

否则按照你的要求处理这种情况。


0
如果你的数组应该始终有一个有效值返回,那么你应该作为备选方案引发一个异常(在你的示例中是第二种情况)。
最终,你可以声明自己的异常类型(类)。

0
我建议使用之前另一个答案中刚谈到的 Maybe (也称为 Option )数据类型。
这个数据类型在Functional Java可用用法:
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已经有了这个功能

假设pieces是一个{{link2:fj.data.List}},那么您的方法可以重写为:

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

另外一点注意:

也许上面的代码看起来相当复杂。IntelliJ IDEA的“闭包折叠”功能可以在这里提供帮助


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