16

在 Java 中创建、抛出和捕获异常是否有任何性能成本?

我计划将“异常驱动开发”添加到一个更大的项目中。我想设计自己的异常并将它们包含在我的方法中,迫使开发人员捕捉并做适当的工作。

例如,如果您有一种方法可以根据名称从数据库中获取用户。

public User getUser(String name);

但是,用户可能为空,并且在使用用户的公共方法之前忘记检查这一点是很常见的。

User user = getUser("adam");

int age = user.getAge();

这将导致 NullPointerException 和崩溃。但是,如果我在返回用户对象之前进行了检查,如果它为 null 并抛出“UserIsNullException”:

public User getUser(String name) throws UserIsNullException;

我强迫实施者思考和行动:

try {

    User user = getUser("adam");

    int age = user.getAge();

}catch( UserIsNullException e) {

}

它使代码对于意外崩溃更加安全,并消除了更多错误。假设该网站每小时有数百名访问者,并且这种设计模式几乎无处不在。

这种设计方法将如何影响性能?收益是否超过了成本,还是仅仅是糟糕的编码?

谢谢你的帮助!

更新!需要明确的是,我的注意力不是包装 NullPointerException,正如我的示例所暗示的那样。目标是强制实现者编写一个 try/catch,避免真正崩溃的头痛,因为:

用户 == 空

被遗忘了。问题涉及比较这两种设计模型:

int age;

try {

User user = getUser("adam");

age = user.getAge();

}catch( UserIsNullException e) {

age = 0;

}

相对:

int age;

User user = getUser("adam");

if( user != null ) {
age = user.getAge();
} else {
age = 0;
}
4

14 回答 14

10

抛出异常会有性能损失,但这通常是可以接受的,因为异常处理代码只在异常情况下执行。如果您开始使用异常来进行流控制,那么您正在扭转通常的情况并使其成为预期的行为。我强烈反对它。

于 2010-02-02T15:10:40.397 回答
8

“这将导致 NullPointerException 和崩溃。”

NullPointerException 是一个异常,可以在catch 中捕获。

所以额外的异常是多余的,除非它增加了代码的清晰度。在这种情况下,它真的没有。实际上,您刚刚获取了一个未经检查的程序员错误异常并将其提升为一个已检查的异常。

为程序员的错误创建一个检查异常实际上意味着你的代码必须显式地处理程序员引入错误的可能性,而他们没有。

于 2010-02-02T15:02:34.600 回答
8

有性能成本,但这不是主要问题。你真的希望你的代码像你的例子一样到处都是 catch 块吗?那太糟了。

通常,Web 应用程序有一个主要的异常处理程序,其中所有未捕获的内容都会结束,至少在出现错误时处理会被干净地切断。随着所有这些异常捕获的进行,您的流程就像从几段楼梯上摔下来一样。

一些例外是您期望并且可以处理的非常特殊的情况。但是,通常会在出现意外或无法控制的问题时弹出异常。从那时起,您的对象可能处于不良状态,因为后续步骤会期望由于异常而没有发生的事情,并且尝试继续只会触发更多异常。最好让意外的异常消失,这样它们就可以被中央处理程序捕获。

于 2010-02-02T15:03:38.190 回答
5

就个人而言,我认为这是一个糟糕且令人讨厌的想法。

鼓励人们检查空值的常用方法是使用注释,如@Nullable(以及它的相反,,@NotNull对于保证返回非空值的函数)。通过在参数上设置类似的注释(以便设置参数预期),质量 IDE 和错误检查器(如 FindBugs)可以在代码没有进行足够检查时生成所有必要的警告。

这些注释在JSR-305中可用,显然还有一个参考实现


就性能而言,创建异常是昂贵的部分(我已经读过这是由于堆栈跟踪填充等原因)。抛出异常很便宜,并且是JRuby 中使用的一种控制转移技术。

于 2010-02-02T15:06:52.183 回答
5

一般来说,异常在 Java中非常昂贵,应该用于流控制!

例外的意义在于描述一些特殊的东西,例如NumberIsZeroException不是真的那么特殊,而PlanesWingsJustDetachedException显然是真正特殊的东西。如果在您的软件中确实存在异常,该用户是null因为存在数据损坏或 ID 号不匹配任何内容或类似的内容,那么可以为此使用异常。

例外还导致偏离“幸福之路”。虽然这不适用于您的示例代码,但有时使用Null Object而不是返回 plain是非常有益的null

于 2010-02-02T15:07:15.010 回答
4

构建堆栈跟踪相当于大约一千条基本指令。它有一定的成本。


即使你不问,很多人可能会告诉你,你设想的方法似乎不是很有吸引力...... :-(

你强加给调用者的代码真的很难看,很难编写和维护。

更具体地说,并尝试理解,我将尝试强调几点。

  • 其他开发人员负责检查从您的 API 收到的用户的无效性(如果文档明确说明)。他的代码会定期在内部进行此类检查,因此他也可以在调用您的 API 后进行检查。更重要的是,这将使他的代码具有一些同质性。

  • 当重用现有异常是适当的,它比创建自己的要好得多。例如,如果调用 API 并请求不存在的用户时出错,则可以抛出 IllegalArgumentException。

于 2010-02-02T15:01:32.827 回答
2

请参阅有关异常性能的答案。

基本上,在您的想法中,您将 RuntimeException NullPointerException 包装到已检查的异常中;恕我直言,我会说这个问题可以在业务级别使用 ObjectNotFoundException 进行管理:您没有找到用户,用户为 null 并且这会产生错误的事实紧随其后。

于 2010-02-02T15:03:22.433 回答
1

空对象可能对您有所帮助。前几天我正在阅读 Martin Fowler 的书“重构”,它描述了使用一个特定的对象来代替返回 null,从而使您不必一直检查 NULL。

我不会尝试在这里解释它,因为它在书中描述得很好。

于 2010-02-02T15:06:10.817 回答
1

我喜欢这种编码风格,因为从使用 API 的人的角度来看,它非常清楚正在发生的事情。有时我什至命名 API 方法getMandatoryUser(String)getUserOrNull(String)区分永远不会返回null的方法和可以返回的方法null

关于性能,除非您正在编写非常延迟关键的代码,否则性能开销将可以忽略不计。然而,值得一提的是,在 try / catch 块方面有一些最佳实践。例如,我相信 Effective Java 提倡在循环之外创建一个 try/catch 块,以避免在每次迭代时创建它;例如

boolean done = false;

// Initialise loop counter here to avoid resetting it to 0 if an Exception is thrown.
int i=0;    

// Outer loop: We only iterate here if an exception is thrown from the inner loop.
do {
  // Set up try-catch block.  Only done once unless an exception is thrown.    
  try {
    // Inner loop: Does the actual work.
    for (; i<1000000; ++i) {
      // Exception potentially thrown here.
    }

    done = true;
  } catch(Exception ex) {
    ... 
  }
} while (!done);
于 2010-02-02T15:03:30.247 回答
1

创建异常、抛出异常(带有堆栈跟踪)、捕获异常然后(最终)垃圾收集该异常比进行简单的 if 检查要慢得多。

归根结底,你可以把它归结为风格,但我认为这是非常糟糕的风格。

于 2010-02-02T15:03:40.247 回答
0

这种设计方法将如何影响性能?收益是否超过了成本,还是仅仅是糟糕的编码?

我说过这种编程风格几乎没有任何好处。Exception 应该理解为(嗯...)exception。它应该只发生在非正常情况下。你如何定义“正常情况”是非常有争议的想法......

于 2010-02-02T15:07:20.100 回答
0

您可以忘记异常并避免此类性能问题。这使您的 API 不言自明。

int getUserAge(String userName) 
{
    int age = 0;
    UserSearchResult result = getUser(userName);
    if (result.getFoundUser())
    {
        User user = result.getUser();
        age = user.getAge();
    }
    return age;
}
于 2010-02-02T15:10:41.287 回答
0

异常成本很高,因为每次抛出异常时,都必须创建和填充堆栈跟踪。

想象一下余额转移操作在 1% 的情况下由于缺乏资金而失败。即使故障率相对较低,性能也可能受到严重影响。有关源代码和基准测试结果,请参见此处

于 2011-02-08T03:49:37.650 回答
0

根据您上次的编辑。我认为(正如 Tony 建议的那样)在这里使用 NullObject 模式会更有用。

考虑第三种情况:

class NullUser extends User {

    public static NullUser nullUser = new NullUser();

    private NullUser(){}

    public int getAge() {
        return 0;
    }
}

//Later...
int age;

User user = getUser("adam"); // return nullUser if not found.
age = user.getAge();         // no catch, no if/null check, pure polymorphism
于 2010-02-02T15:21:46.303 回答