我们什么时候需要创建自己的Java异常类?

45

从设计和实践的角度来看,我们应该在何时创建和使用自定义Java异常类而不是Java中已经预定义的异常类呢?

在一些应用程序中,我看到几乎总是会创建自定义异常类,并尽可能使用原生的Java异常。而另一方面,有些应用程序为(几乎)所有内容定义自定义异常。

6个回答

50

来自异常处理最佳实践:

尽量不要创建新的自定义异常,如果它们对客户端代码没有有用的信息。

以下代码有什么问题?

public class DuplicateUsernameException extends Exception {}

除了指示性的异常名称之外,它不向客户端代码提供任何有用的信息。不要忘记Java异常类与其他类一样,您可以添加您认为客户端代码将调用以获取更多信息的方法。

我们可以向 DuplicateUsernameException 添加有用的方法,例如:

public class DuplicateUsernameException
    extends Exception {
    public DuplicateUsernameException 
        (String username){....}
    public String requestedUsername(){...}
    public String[] availableNames(){...}
}
新版本提供了两个有用的方法:requestedUsername(),它返回请求的名称,以及availableNames(),它返回一个类似于所请求的用户名的可用用户名数组。客户端可以使用这些方法来通知请求的用户名不可用,其他用户名可用。但是如果您不打算添加额外信息,则只需抛出标准异常。
throw new IllegalArgumentException("Username already taken");

54
不要只抛出 Exception!这意味着你必须捕获 Exception,而这也意味着你会捕获所有的 RuntimeException,因此也包括所有的 NPE(NullPointerException)等! - fge
2
它并没有向客户端代码提供任何有用的信息,除了一个指示性的异常名称:但是,如果您需要为重复的名称执行特定的错误处理,比如提示输入不同的名称,那么将非常有用。 - Raedwald
在使用Java构建REST API服务器时,为不同类型的错误设置单独的异常是否更合理呢?这样我们就可以捕获错误类型并将其消息与适当的状态代码一起发送(我不想将HTTP状态代码与我的域异常混合)。 - Amirhosein Al

12
从良好的设计/实践角度来看,何时应该创建和使用自定义Java异常类,而不是Java中已经预定义的异常类?
当现有的异常名称无法满足您的需求时。
另一个设计考虑因素是扩展“好”的异常类;例如,如果引发与I/O相关的异常,则应理想地继承IOException;如果异常表示程序员错误,则应继承RuntimeException(即使您的异常未经检查)。
引发自定义异常也允许您以更精确的方式处理异常;例如,如果您定义了继承IOException的FooException,则可以对其进行特殊处理:
try { ... }
catch (FooException e) { ... } // Catch it _before_ IOException!
catch (IOException e) { ... }

此外,异常类和其他类一样,因此您可以添加自定义方法等; 例如,Jackson定义了JsonProcessingException继承IOException。如果您捕获它,可以使用.getLocation()获取解析错误的位置信息。


7
当您期望能够以编程方式处理异常时,显然很容易为不同的异常类型创建单独的catch语句,例如:
try{    
  buyWidgets();
}
catch(AuthenticationException ex)
{
  promptForLogin();
}
catch(InsufficientFundsException ex)
{
  promptToRefillAccount();
}
//let other types of exceptions to propagate up the call stack

关于上述内容是否构成错误的异常使用

虽然异常处理比if-else语句更消耗CPU(主要是由于构建堆栈跟踪的成本),但成本是相对的,应该在特定用例的上下文中进行评估。并不是每段代码都需要速度非常快,一些人发现阅读和测试条件语句更加繁琐。例如,几乎所有事务管理器都使用异常来实现提交-回滚-重试惯用语法。(尝试编写一个事务重试切面而不捕获异常)

此外,人们应该遵循关注点分离原则:并不是每段代码都需要处理每种可能的情况。在购买小部件时没有登录是否属于异常情况,这取决于应用程序和应用程序代码库中的具体位置。例如,您可以拥有一个为已登录用户提供操作的服务。对于该服务中的方法来说,处理身份验证并没有任何意义 - 相反,这些方法会期望调用链中较早的代码确保用户已经过身份验证,因此如果未通过身份验证,则简单地抛出异常。因此,对于那些没有登录的方法来说, 异常情况。


4
异常不应该被用于流程控制。关于这个问题的一个很好的解释可以参考这里:https://dev59.com/wHI_5IYBdhLWcg3wDOnW。 - stringy05
1
那是过于简单化了。请看我上面的编辑。 - Nikita

4

当您使用自己的异常时,可以为代码库增加价值

很简单。价值可能包括:

  • 在更高级别上捕获可能会更容易,例如当所有异常都有一个共同的基类型时
  • 您的异常携带附加数据(我们在自己的异常中包含NLS键,使更高层次的代码知道如何考虑I18N向人类用户提供消息)

2
我认为如果有一个例子会更有帮助 ;) - davidxxx
@GhostCat 请举个例子... - Gaurav
我必须承认:我举了一个例子。展示如何使用异常来携带nls信息会有什么帮助呢?我提供的例子概述了为什么异常在概念上可能会更加丰富。 - GhostCat

2
我通常会创建自定义异常,如果需要传达更多信息而不仅仅是错误消息。例如特定的错误代码或实际与期望值之间的差异。这些也可以拥有它们自己的getter,这样您可以编程地检索这些字段,而无需解析字符串消息,因为如果您更改消息的文本,它可能会中断(或将其翻译成不同的语言)。
如果您制作自己的异常,我建议扩展常见的JDK内置异常,这样您的API可以说“throws IOException”,但实际上会抛出“MycustomIOException”。这样,您的API用户不必知道您自己的自定义版本,除非他们想要。

0

在应用程序中使用自定义异常是很好的选择,其中一个可以是应用程序级别的顶层自定义异常,其他可以是在模块/包级别的。如果你的应用程序中有一些特定的函数/操作,需要告知用户在操作期间是否发生了任何异常,那么最好为该操作添加自定义异常。这样便于调试/排查问题。


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