如何更好的使用Java异常,看这篇就对了!

 

点击上方 "程序员小乐"关注, 星标或置顶一起成长

每天凌晨00点00分, 第一时间与你相约

每日英文

To live a beautiful life, one must be tolerant, without complaint or explanation.

要生活得漂亮,需要付出极大忍耐,一不抱怨,二不解释。

每日掏心

人活着,有一种煎熬的痛苦,并不是不曾拥有,也不是早已失去,而是患得患失。

来自:XuePeng77 | 责编:乐乐

链接:my.oschina.net/u/2450666/blog/1585393

程序员小乐(ID:study_tech) 第 823 次推文   图片来自百度

往日回顾:IDEA 的这款插件真是牛掰了,代码那都不是事!

     

   正文   

Java的基本理念是,“结构不佳的代码不能运行”

        —— Java编程思想

什么时候使用异常

发现错误的理想时机是在编译阶段,也就是试图运行程序之前。然而,编译阶段并不能找出所有的错误,余下的问题必须在运行期间解决。这就需要错误源能够通过某种方式,把适当的信息传递给某个接收者——该接收者将知道如何正确处理这个问题。

举个例子,当一个方法遇到一种情况,这种情况下,它不能满足约定,这时应该如何处理?

传统的做法是方法应该返回某种错误码。调用者被迫区检查错误,如果调用者也不能处理错误,那就给调用者的调用者返回一个错误码……

程序员并不总是检查和传递返回的错误码,结果错误没有被检测到,导致后面的严重破坏!

C以及其他早期语言常常具有多种错误处理模式,这些模式往往建立在约定俗成的基础之上,而并不属于语言的一部分。

只针对异常的情况才使用异常

从一个反面教材开始:

try {
	int i = 0;
	while (true)
		range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {
}

上面这种情况就是乱用异常的典型,它的构想是非常拙劣的。当这个循环企图访问数组边界之外的第一个数组元素时,用throw、catch和忽略ArrayIndexOutOfBoundsException的手段来达到终止无限循环的目的,对于任何一个合格的程序员来说,它的实现用下面的标准模式一看便知:

for (Mountain m : range)
	m.climb();

异常机制的设计初衷是用于不正常的情形。所以很少会有JVM实现试图对它们进行优化,使得异常与显示的测试一样快速,例如for等。把代码放到try-catch块中,反而阻止了JVM实现本来可能要执行的某些特定优化。这个例子说明:

异常应该只用于异常的情况下,他们永远不应该用于正常的控制流;

设计良好的API不应该强迫它为了正常的控制流而使用异常;

Java的异常架构

所有的异常类都派生自Throwable。当发生来某种异常,而这种异常不是期望应用程序处理的,比如内存耗尽等,则JVM会抛出Error。这种异常不推荐程序员使用。

一般程序员使用的异常属于Exception类的异常,他们分为两种:

未检查异常(unchecked exception),属于RuntimeException的子类,它属于不需要也不应该被捕获的可抛出结构。用运行时异常来表明编程错误。大多数的运行时异常都表示前提违例(precondition violation),意思是API的客户没有遵守API的规范建立的约定,比如要求参数不能为Null,但还是传递Null参数,实现的所有未检查异常(未受检异常)都应该是RuntimeException的子类;

已检查异常(checked exception),属于Exception的子类,也称为受检异常,如果期望调用者能够适当地恢复,对于这种情况就应该使用它。通过抛出异常,强迫调用者在一个catch里处理该异常,或将它传播出去,尤其在设计API时,设计者让API用户面对受检的异常;

异常也是个对象,可以在它上面定义任意的方法。这些方法的主要用途是为捕获异常的代码提供额外的信息,特别是关于引发这个异常条件的信息。

避免不必要的使用受检的异常

受检异常强迫使用该API的用户必须catch异常,大大增加来程序的可靠性。但是多度使用会使API使用起来非常不方便。用户必须声明它抛出的这些异常,或者让它们传播出去,无论哪种方法,都给API用户增添来负担。

那什么时候使用受检异常比较合适呢?

如果,正确的使用了API,但并不能组织这种异常条件的发生,并且一旦产生异常,使用API的用户会立即采取有用的动作,这种情况就是使用受检异常的典型场景。

例如,API的设计人员可以尝试着问自己:API用户将如何处理该异常,会有比下面代码更好的方式吗?

catch (CheckedException e) {
	throw new AssertionError();
}

下面这种做法如何?

catch (CheckedException e) {
	e.printStackTrace();
	System.exit(1);
}

如果使用API的用户无法做的比这更好,那还是使用未受检异常更为合适。

这段代码的意思为,如果这个异常发生了,程序注定会停止,或者要完成的事情注定不可以完成,那直接抛出未受检异常让程序中断,暴露出问题更为合适。

例如,在缴费电话费时,由于余额不足导致缴费失败,可以抛出受检异常,并提供一个方法,查询所需的金额等。

优先使用标准的异常

代码重用是值得提倡的,这是一条通用的规则,异常也不例外。

Java提供来一组基本的未受检异常,它们满足了绝大多数API的异常需求。

异常场合
IllegalArgumentException非null的参数值不正确
IllegalStateException对于方法调用而言,对象状态不合适
NullPointerException在禁止使用null的情况下参数值为null
IndexOutOfBoundsException下标参数值越界
ConcurrentModificationException进行并发修改的情况下,检测到并发修改对象
UnsupportedOperationException对象不支持用户请求的方法

抛出与抽象相对应的异常

如果方法抛出的异常与它所执行的任务没有明显联系,这种情形将会使人不知所措。

更高层的实现应该捕获底层的异常,同时抛出可以按照高层抽象进行解释的异常。

如下所示:

try {
} catch (LowerLevelException e) {
	throw new HigherLevelException(...);
}

或者:

Iterator i = ...
	try {
	return i.next()
} catch (NoSuchElementException e) {
	throw new IndexOutOfBoundsException("Index: " + index);
}

还有一种特殊的异常转义形式,称为异常链(exception chaining),如果低层的异常对于调试导致高层异常的问题非常有帮助,使用异常链就很合适。底层异常的原因可以传给高层异常,高层异常提供访问方法,来获得底层异常:

try {
	
} catch (LowerLevelException cause) {
	throw new HigherLevelException(cause);
}

尽管异常转移与不佳选择地从底层传递异常的做法相比有所改进,但是它也不能被滥用。

如果可能,处理来自跌穿那个异常的最好做法是,在调用底层方法之前,前确保他们会执行成功,从而避免它们抛出异常。

努力保持失败的原子性

当对象抛出异常后,通常期望这个对象仍然保持在一种定义良好的可用状态之中,即使失败是发生在执行某个操作过程中间,对于受检异常而言,这点尤为重要,因为API用户期望能从这种异常中恢复。

一般而言,失败的方法调用应该使对象保持在被调用之前的状态。具有这种属性的方法被称为具有失败原子性(failure atomic)。

有几种方法可以实现这种效果:

最简单的办法是设计一个不可变对象;

设计处理过程的顺序,使得任何可能会失败的部分都在对象状态改变之前发生;

编写一段恢复代码(recovery code);

在对象的一份临时拷贝上执行操作;

异常文档的重要性

始终要单独声明受检的异常,并且利用JavaDoc的@throws标记,准确地记录下抛出每个异常的条件。

永远不要throws Exception,或者throws Throwable,它们会掩盖该方法抛出的任何其他异常。

未受检的异常通常代表编程上的错误,让API用户了解所有这些错误都有助于帮助他们避免调用错误。对于方法可能抛出的未受检异常,如果将这些异常信息很好的组织成列表文档,就可以有效地描述出这个方法被成功执行的前提条件(precondition)。

对于接口中的方法,在文档中记录下它可能抛出的未受检异常显得尤为重要。这份文档构成了该接口的通用约定(general contract)的一部分,它指定来该接口的多个实现必须遵守的公共行为。

使用JavaDoc的@throws标签记录下一个方法可能抛出的每个未受检异常,但是不要使用throws关键字将未受检的异常包含在方法的声明中。

如果一个类中的许多方法处于同样的原因抛出同一个异常,在类的文档注视中,堆这个异常建立文档。

不要忽略异常

空的catch达不到应有的目的,至少,catch块也应该包含一条说明,解释为什么忽略这个异常。

希望不要忽略异常。

附录A

java.lang.RuntimeException的直接子类有这些:

  1. AnnotationTypeMismatchException

  2. ArithmeticException

  3. ArrayStoreException

  4. BufferOverflowException

  5. BufferUnderflowException

  6. CannotRedoException

  7. CannotUndoException

  8. ClassCastException

  9. CMMException

  10. ConcurrentModificationException

  11. DataBindingException

  12. DOMException

  13. EmptyStackException

  14. EnumConstantNotPresentException

  15. EventException

  16. IllegalArgumentException

  17. IllegalMonitorStateException

  18. IllegalPathStateException

  19. IllegalStateException

  20. ImagingOpException

  21. IncompleteAnnotationException

  22. IndexOutOfBoundsException

  23. JMRuntimeException

  24. LSException

  25. MalformedParameterizedTypeException

  26. MirroredTypeException

  27. MirroredTypesException

  28. MissingResourceException

  29. NegativeArraySizeException

  30. NoSuchElementException

  31. NoSuchMechanismException

  32. NullPointerException

  33. ProfileDataException

  34. ProviderException

  35. RasterFormatException

  36. RejectedExecutionException

  37. SecurityException

  38. SystemException

  39. TypeConstraintException

  40. TypeNotPresentException

  41. UndeclaredThrowableException

  42. UnknownAnnotationValueException

  43. UnknownElementException

  44. UnknownTypeException

  45. UnmodifiableSetException

  46. UnsupportedOperationException

  47. WebServiceException

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。

欢迎各位读者加入订阅号程序员小乐技术群,在后台回复“加群”或者“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

强软弱虚引用,只有体会过了,才能记住!

互联网后端基础设施,看了都说好!

(二)手把手教你 SpringBoot + SpringCloud —— 使用Eureka实现服务注册与发现!

关注订阅号「程序员小乐」,收看更多精彩内容

嘿,你在看吗

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值