目录
  1. 1. 前言
  2. 2. 异常
  3. 3. 异常分类
    1. 3.1. checked异常与unchecked异常
  4. 4. try代码块
    1. 4.1. 和return的关系
    2. 4.2. try与锁的关系
  5. 5. 异常的抛与接
《码出高效》系列笔记(三):异常与日志

良好的编码风格和完善统一的规约是最高效的方式。


前言

本篇汲取了本书中较为精华的知识要点和实践经验加上读者整理,作为本系列里的第一篇章第三节:面向对象的其他知识点篇。

本系列目录

异常

处理异常程序时,需要解决以下3个问题:

  1. 哪里发生异常
  2. 谁来处理异常
  3. 如何处理异常

无论采用哪种方式处理异常,都严禁捕获异常后什么都不做或打印一行日志了事

异常分类

JDK中定义了一套完整的异常机制,所有异常都是Throwable的子类,分为Error(致命异常)和Exception(非致命异常)。其中Exception又分为checked(受检型异常)和unchecked(非受检型异常)。

checked异常与unchecked异常

checked异常是需要在代码中显示处理的异常,否则会编译出错。

  • 其中无能为力、引起注意型这类常见的有SQLException,即使再做更多次的重试对异常的解决也没有任何帮助,一般处理方式是保存异常现场,供工程师介入解决。
  • 力所能及、坦然处置型。如发生未授权异常(UnAuthorizedException),程序可以跳转至权限申请页面。

unchecked异常是运行时异常,它们是都继承自RuntimeException,不需要程序进行显式的捕捉和处理,该类异常可以分为以下3类:

  • 可预测型异常(Predicted Exception):常见的大家都很熟悉包括IndexOutOfBoundsException、NullPointException等,此类异常不应该产生或者抛出,而应该提前做好边界检查、空指针判断处理等。显式的声明很蠢
  • 需捕捉异常(Caution Exception):例如在使用Dubbo框架在进行RPC调用时产生的远程服务超时异常DubboTimeoutException,此类异常是客户端必须显示处理的异常,不应该出现因产生该异常而导致不可用的情况,一般处理方法是重试或者降级处理。
  • 可透出异常(Ignored Exception):主要是指框架或系统产生的且会自动处理的异常,而程序无需关心。例如Spring框架中抛出的NoSuchRequestHandlingMethodException异常,Spring框架会自己完成异常的处理,默认将自身抛出的异常自动映射到合适的状态码,比如启动防护机制跳转到404页面。

异常分类结构

针对上图的结构,下面结合旅行的实例来说明一下异常分类。

第一,机场地震,属于不可抗力,对应异常分类中的Error。平时在出行时无需考虑该因素。

第二,堵车属于checked异常,应对这种异常,我们可以提前出发,或者改签机票。而飞机延误异常,虽然也需要check,但是我们无能为力,只能持续关注航班动态。

第三,忘带护照,可提前预测的异常,在出发前检查避免。去机场路上厕纸抛锚,突发异常难以预料,但是必须处理,属于需要捕获的异常,可以通过更换交通工具应对。检票机器故障属于可透出型异常,交由航空公司处理,我们无须关心。

try代码块

try-catch-finally是处理程序异常的三部曲。当存在try时,可以只有catch代码块,也可以只有finally代码块,就是不能单独只有try这个光杆司令。

  1. try代码块:监视代码执行过程,一旦发现发现异常则直接跳转至catch,如果没有catch,则直接跳转至finally。
  2. catch代码块:可选执行的代码块,如果没有异常发生则不会执行;如果发现异常则进行处理或向上抛出。这一切都在catch代码块中执行。
  3. finally代码块:必选执行的代码块,不管是否有异常产生,即使发生OutOfMemoryError也会执行,通常用于处理善后清理工作。如果finally代码块没有执行,那么有三种可能:
    • 没有进入try代码块
    • 进入try代码块,但是代码运行中出现了死循环或死锁状态
    • 进入try代码块,但是执行了System.exit()操作

return的关系

finally是在return表达式运行后执行的,此时将要return的结果已经被暂存起来,待finally代码块执行结束后再将之前的暂存的结果返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static int x = 1;
static int y = 10;
static int z = 100;

public static void main(String[] args) {
int value = finallyReturn();
System.out.println("value = " + value);
System.out.println("x = " + x);
System.out.println("y = " + y);
System.out.println("z = " + z);
}
public static int finallyReturn() {
try {
// todo
return ++x;
} catch (Exception e) {
return ++y;
} finally {
return ++z;
}
}

打印的结果

1
2
3
4
value = 101
x = 1
y = 11
z = 101

以上的结果说明:

  1. 最后return的动作是由finally代码块中的return ++z完成的,所以方法返回的结果101。
  2. 语句return ++x中的++x被成功执行,所以运行结果是2。
  3. 如果有异常抛出,那么运行结果将会是y=11,而x=1。

finally代码块中使用return语句,使返回值的判断变得复杂,所以避免返回值不可控,我们不要在finally代码块中使用return语句。

try与锁的关系

lock方法可能会抛出unchecked异常,如果放在try中,必然触发finally中的unlock方法执行。对未加锁的对象解锁会抛出unchecked异常。所以在try代码块之前调用lock方法,避免由于加锁失败导致finally调用unlock抛出异常。

1
2
3
4
5
6
7
8
9
Lock lock = new XxxLock();
preDo();
try {
// 无论加锁是否成功,unlock都会被执行。
lock.lock();
doSomething();
} finally {
lock.unlock();
}

所以在try代码块之前调用lock方法,避免由于加锁失败导致finally调用unlock方法抛出异常。lock.lock();这段代码应该移到try的上方。

异常的抛与接

文章作者: MatthewHan
文章链接: https://Matthew-Han.github.io/post/43892940-eb34-11e9-8e01-011debd96741/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Espada
打赏
  • 打赏宁 🐴 呢!给 👴 爬!

评论