从设计和实践的角度来看,我们应该在何时创建和使用自定义Java异常类而不是Java中已经预定义的异常类呢?
在一些应用程序中,我看到几乎总是会创建自定义异常类,并尽可能使用原生的Java异常。而另一方面,有些应用程序为(几乎)所有内容定义自定义异常。
从设计和实践的角度来看,我们应该在何时创建和使用自定义Java异常类而不是Java中已经预定义的异常类呢?
在一些应用程序中,我看到几乎总是会创建自定义异常类,并尽可能使用原生的Java异常。而另一方面,有些应用程序为(几乎)所有内容定义自定义异常。
来自异常处理最佳实践:
尽量不要创建新的自定义异常,如果它们对客户端代码没有有用的信息。
以下代码有什么问题?
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");
try { ... }
catch (FooException e) { ... } // Catch it _before_ IOException!
catch (IOException e) { ... }
此外,异常类和其他类一样,因此您可以添加自定义方法等; 例如,Jackson定义了JsonProcessingException
继承IOException
。如果您捕获它,可以使用.getLocation()
获取解析错误的位置信息。
try{
buyWidgets();
}
catch(AuthenticationException ex)
{
promptForLogin();
}
catch(InsufficientFundsException ex)
{
promptToRefillAccount();
}
//let other types of exceptions to propagate up the call stack
关于上述内容是否构成错误的异常使用
虽然异常处理比if-else语句更消耗CPU(主要是由于构建堆栈跟踪的成本),但成本是相对的,应该在特定用例的上下文中进行评估。并不是每段代码都需要速度非常快,一些人发现阅读和测试条件语句更加繁琐。例如,几乎所有事务管理器都使用异常来实现提交-回滚-重试惯用语法。(尝试编写一个事务重试切面而不捕获异常)
此外,人们应该遵循关注点分离原则:并不是每段代码都需要处理每种可能的情况。在购买小部件时没有登录是否属于异常情况,这取决于应用程序和应用程序代码库中的具体位置。例如,您可以拥有一个为已登录用户提供操作的服务。对于该服务中的方法来说,处理身份验证并没有任何意义 - 相反,这些方法会期望调用链中较早的代码确保用户已经过身份验证,因此如果未通过身份验证,则简单地抛出异常。因此,对于那些没有登录的方法来说,是 异常情况。
当您使用自己的异常时,可以为代码库增加价值。
很简单。价值可能包括:
在应用程序中使用自定义异常是很好的选择,其中一个可以是应用程序级别的顶层自定义异常,其他可以是在模块/包级别的。如果你的应用程序中有一些特定的函数/操作,需要告知用户在操作期间是否发生了任何异常,那么最好为该操作添加自定义异常。这样便于调试/排查问题。
Exception
!这意味着你必须捕获Exception
,而这也意味着你会捕获所有的RuntimeException
,因此也包括所有的 NPE(NullPointerException)等! - fge