A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

写在最前最近笔者在撰写JavaWeb与自动化相结合的教程,上篇入口在这里,第二篇还在创作中,在发布之前,让我们先来讨论一个Java的重要技能,Exception。
实现程序的运行是所有初级的程序员所追求的,Thinking in Java 因此成为了很适合入门的一本书,然而随着代码行数的累积,越来越多的坑也随之到来。此时,对基础知识更深层次的理解就尤为关键。在JavaWeb与自动化结合的应用中,无脑抛出异常会导致代码的冗余与羸弱,今天发的这篇文章将仔细地对Exception的运用进行分析。
需要注意的是,本篇文章并不是对如何抛出异常的基础进行讲解,需要读者对Exception机制有一定了解,文中部分用例来自Effective Java,在这里同时向读者推荐这本书作为Java进阶的重要工具,文末附录中有笔者Exception部分的英文笔记供大家参考。
使用Exception的情景不要在类似迭代的循环中使用Exception,尤其是涉及ArrayIndexOutOfBounds,如下所示:
try {    int i = 0;    while(true)        array[i++].doSomething();} catch(ArrayIndexOUtOfBoundsException e) {}复制代码主要因为此时使用try-catch有三点显而易见的坏处:
  • 这样做违背于JVM设置exception处理的原则,JVM会花费更多的时间来处理。
  • 把Code放在try-catch语句中使得一些JVM运行中的优化被封禁。
  • 规范的迭代写法是经过优化的,通过JVM的内部处理,避免了很多赘余的检查机制,是更合适的选择。
如果我们在try-catch语句中调用了另一个数组,这个数组中出现了ArrayIndexOutOfBounds的异常,其中的bug就会被catch exception所蒙蔽。相反,标准的迭代写法会及时的终止线程的执行,报出错误并且给出追踪错误的路径让程序员更轻松地定位bug的来源。
现在我们通过Java的Iterator接口来看一下标准迭代写法,在标准的迭代写法中,我们利用hasNext()作为state-testing判断方法,来实现state-dependent方法next(),代码如下:
for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) {    Foo foo = i.next();    //...}复制代码综上所述,Exception是为了异常或者说例外的情况而准备的,不应该在普通的语句中使用,并且程序员也不该写出强迫他人在正常流程的语句中使用Exception的API。
Checked与Unchecked的区别在Java中Throwable是Exception与Error的父类,而在Effective Java书中,Throwable被分为了以下三类:
  • Checked Exceptions
  • Runtime Exceptions
  • Errors
其中,2和3都是Unchecked Throwable,所以在我们分析Java的异常类时,从Checked与Unchecked两个逻辑角度来分析会更加清晰。
Checked Exception指那些在编译过程中会检查的,这类错误在运行中是“可恢复的”。我们需要在写程序时将其抛出,换而言之,这些异常应该并不是由程序员所导致,而是类似”例行检查“。
相反,Runtime Exception指的就是程序员本身制造出来的错误,在文章的第一部分中我们已经明确指出,此类错误不应该被抛出,而应该由程序员自己去修复。需要注意的是,一般来说,我们自己设计的Exception应该作为Runtime Exception的直接或者间接子类。如果你对Exception理解得比较浅,暴力地把Runtime Exception的子类背下来,对debug的帮助也相当大,可以快速定位代码中的问题。
Errors与Exception不同,他是与JVM相关的,当你在写算法时看到栈溢出,那并不是你对语言的理解导致你的代码出现漏洞,而是你的数据结构使得JVM出现resource deficiency或invariant failures使得程序无法继续执行,所以看到Errors的时候,我们也不应将其抛出,而是应该对代码结构进行修改处理。
综上所述,如果在运行中可恢复,那么我们就应该将这种Checked Exception抛出。当不清楚该如何做的时候,抛出Runtime Exception。重要的是,不要定义既不是Checked Exception子类也不是Runtime Exception子类的Throwable,并且记得在你自定义的Checked Exception中加入方法使代码能在运行中恢复。
Checked Exception的使用技巧我们经常会遇到这种问题,在一个方法中,有一行代码需要抛出Exception,我们需要将他包裹在try-catch语句中。在Java8之后,我们在使用此API时必须抛出这个异常,这极大地降低了我们代码的质量。
解决这个问题最简单的方法可能就是我们在运行此方法是不返回任何值,但是如果这样做我们就少了很多通过此方法返回信息和数据的机会。
因此我们提供了另一种解决方式,那便是通过将需要抛出Checked Exception的方法拆为两个方法,使其转变为一个Unchecked Exception。第一个方法通过返回一个boolean值来指明此Exception是否应该被抛出,第二个再进行剩余的操作。下面是一个转变的简单例子。
包裹在try-catch中的语句:
try {    ted.read(book);} catch (CheckedException e) {    //...do sth.}复制代码下面是改造后的代码:
if (ted.understand(book)) {    ted.read(book);} else {    //...do sth.}复制代码简单来说,就是本来是再Ted”读“这个方法中抛出他看不懂这个书的异常,但我们将其拆分为”是否理解“与“读”两个方法对其进行重构,来避免try-catch的运用。
总的来说,重构Checked Exception是为了代码更简洁更可靠,避免了对Checked Exception的过度使用,因为过度使用会导致API对使用者很不友好。在遇到上面所说的情况时,首先考虑能否使用返回值为空的方法,因为这是最直接最简单的解决方式。
优先使用标准库中的Exception使用Java库中提供地Exception有三大好处:
  • 使你的API更容易地被学习与使用,因为大多数程序员都了解标准的异常
  • 让使用了你的API的程序阅读起来更轻松
  • 更少地占用内存并且更快地对Class进行加载(JVM)
不要直接重用Exception, RuntimeException, Throwable或是Error这些父类,常用的Exception在下表中列出。
Exception使用场景
IllegalArgumentException不匹配的非空参数的传递
IllegalStateException未初始化的对象(对象状态不匹配)
NullPointerException在未预期的情况下遭遇空指针
IndexOutOfBoundsException索引参数超出范围
ConcurrentModificationException多线程对同一个对象进行修改
UnsupportedOperationException此对象不支持对此方法的引用需要注意的是,重用的Exception一定要与记录的语义一致,在文档中详细说明,并不只是简单地匹配Exception的名字。
结语除了上面详述的几点外,还要注意的是,首先,每个方法抛出的异常都要有文档。其次,保持异常的原子性。最重要的是,千万不要在catch中忽略掉捕获到的异常
关于异常处理对于很多人来说只是Alt+Enter,但是在代码优化阶段经常很让人头疼,希望本文能使大家有所启发,对于接下来教程中的一些代码有更好的理解,也欢迎大家提问,共同提高。


【转载】
作者:Daniel_D
链接:https://juejin.im/post/5bfd0e22f265da611179ff54



2 个回复

正序浏览
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马