软件构造(7)错误与异常处理

本节是对6.2节的一个总结,便于日后复习

6.1主要说明了Robustness and Correctness(Reliability)的重要性,下面主要介绍异常处理,断言,防御式编程的具体操作。

1 Error and Exception in Java


由上图可知,Error和Exception都是继承于Throwable接口,其中ErrorRuntimeExceptionUnchecked Exception;Exception中除了RuntimeException之外的都是Checked Exception。
Unchecked表明编译器对这一类的错误或异常不检查
Checked表明编译器对这一类的异常会做检查处理,后面会详细提及。

1.error

The Error class describes internal system errors and resource exhuastion situations inside the Java runtime system that rarely occur

error是用来描述内部系统错误和Java在运行时的资源耗尽。对于这一类问题,我们除了对用户给出一些提示之外是无能为力的,因此自然也不需要抛出此类错误。

1.1 sorts of error
  • 用户输入错误
  • 设备错误
  • 物理限制
1.2 Some typical errors

1.VirtualMachineError:OutofMemoryError, StackOverflowError, internalError
2.LinkageError:NoClassDefFoundError

a class has some dependency on another class;however, the latter class has incompatibly changed after the compilation of the former class
一个类依赖于另一个类,而后者在编译完后发生了改变

Exception(重点)

此类问题通常是由我们自己写的程序所导致的问题,可以对其进行捕获和处理。
异常:程序执行中的非正常事件,导致程序无法再按预想的流程执行。异常可以将错误信息传递给上层调用者,并报告“案发现场”的信息。
如果一个方法无法以一种正常的方式完成它的任务,Java还提供另一种退出机制,即异常机制
使用异常可以使得正常逻辑的代码与错误处理代码分离。

Sorts

我们知道异常可以分为Runtime异常和其他异常。
Runtime异常一般是由程序员在代码里处理不当所造成的,是可预见、可控的,应尽力避免的。而其他异常则是由外部原因造成的,可以预见但一般不可控,因此需要我们进行一些处理。

Runtime异常常见的有类型转换异常,数组越界异常,空指针访问异常等等,而这些都是程序员在编写代码时就应清晰认识并避免的。(所以如果抛出这一类异常,就有些“此地无银三百两”,我知道错误来源于我的代码,但是我不改,属于推卸责任。)
其他类型的异常常见的类型有EOF异常,FileNotFound异常,找一个类名输入错误的类等等此类异常通常问题出现在外部,因此在必要时需要抛出以提醒用户或者自行处理

Unchecked and Checked

对于Unchecked异常而言,不需要对其做处理,编译可以通过,但在执行时可能会出现,此时表明这是一个潜在的bug,需要修改源代码或其他操作。(比如引用空指针,数组越界访问等,编译器不检查此类错误)类似与动态类型检查,只在运行时检查。
Unchecked Exception:NumberFormatException, ClassCastException,IllegalArgumentException, IllegalStateException(无效状态异常,在方法被调用时,程序处于不能被该方法调用的状态), NoClassDefFoundError
对于Checked异常,我们必须对其进行捕获并指定错误处理器handler,否则编译通不过,类似与静态检查

我们应该尽量使用Unchecked异常来处理编程错误,因为unchecked exception 不用使客户端代码显式的去处理它们,它们自己会在该出现的地方挂起程序并打印出错误信息。
当客户端对某种异常无能为力时,可以采用unchecked异常。
当错误可预料,但无法预防(RuntimeException属于可预防的)时,但可以有手段从中恢复,此时应该使用checked exception。

如何通过throws来声明checked异常

异常也是方法和客户端之间spec的一部分,在前置条件中刻画。

程序员必须在方法的spec中明确写清本方法会抛出的所有checked 异常以便于客户端加以处理。而unchecked异常则不需要在spec中写出,因为没有意义。
当前方法的所抛出的以后可能来自两方面,一方面是从其他函数所传来的异常,一种是自己造出来的异常。当程序遇到checked异常没有handler时,程序就终止执行了。
根据LSP原则(前置更松,后置更紧),如果子类型中重写了父类型中的函数,那么子类型中方法抛出的以后不能比父类型所抛出的异常类型更宽泛。子类型可以抛出更加具体的异常,也可以不抛出异常(不一定有异常,所以可能不需要抛出)。而如果父类型的方法为抛出异常,那么子类型的方法也不能抛出异常(不然调用父类的程序无法处理子类型的异常,因为不符合后置条件)。子类型参数逆变,返回值(也包括异常,如果有)协变。

如何使用throw来抛出异常

两种方式均可。

如果仅抛出异常而不处理,则需要用throws来声明,便于后续处理。
在这里插入图片描述

而对于unchecked 异常,不需要抛出声明(我的理解是后续也不会处理这个unchecked异常,所以抛出没有意义。只有当前情况无法处理这个异常而期待后续handle时,才需要抛出,让其他方法去handle)

如何创建一个异常类

当JDK提供的exception类无法充分描述你的程序错误时,可以创建自己的异常类。在该类中,习惯上同时给出一个默认构造函数和一个包含详细信息的构造函数。

在创建异常类时,也可以将其他异常作为参数传入。

创建异常类时,尽可能为客户端着想,提供尽可能详细的,能够解决问题的信息。

如何捕获异常

我们可以采用try-catch。
前文提及,当异常发生后,若找不到handler(处理器),那么程序就会终止,并在控制台打印stacj trace。

当try块抛出在catch子句中指定的异常时,将忽略出现异常位置后面的代码,直接跳到catch子句处。
当无异常抛出时,catch子句不执行
如果抛出的异常,在catch语句中找不到匹配的异常处理(handler),则终止,前文多次提及。
如果你catch了一个异常,那么这个异常就不需要声明了
在这里插入图片描述
当然,你也可以不handle,在签名处声明这个异常。
throws 和 try-catch二选一。
Tips:

  1. 尽量在发生时进行处理,承担责任
  2. 如果实在处理不了,将其抛出,提醒上家处理,或由客户端处理
  3. 如果父类型中的方法没有抛出异常,那么子类型中发生的异常必须全部捕获。
  4. 子类型方法中不能抛出比父类型方法更多的异常
  5. catch子句的匹配机制与switch-case相同,所以从上到下,尽量由细到粗。

在catch语句中还可以抛出异常,这么做的目的在于,更改exception的类型,更方便客户端获取错误并处理;同时也使得客户端不依赖于自己不感兴趣的底层异常;同时也可以防止rep exposure。

如何使用finally

背景:当异常抛出时,方法中正常执行的代码会被终止,如果异常发现前曾申请过某某些资源,那么异常发生后这些资源应该要被恰当的清理,不然会占用内存或者在成其他错误。




总之,具体流程就是
1.有没有异常,有转3,无转2
2. finally语句,继续执行
3. 有没有匹配到,有转4,无转5
4. 处理并转2
5. 执行finally语句后终止

注意2和5的区别即可。

这里返回false,即最终执行了finally(可能和线程啥的有关?不懂?)
try语句没有被执行到 or 在try块中有System.exit(0)等结束JVM的语句时,finally中的语句不会执行。
找到一篇博客总结的不错
Java finally语句到底是在return之前还是之后执行?
贴一下结论:

  1. finally语句在return语句执行之后return返回之前执行的。
  2. finally块中的return语句会覆盖try块中的return返回
  3. 如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变也可能不变。
  4. try块里的return语句在异常的情况下不会被执行,这样具体返回哪个看情况。
  5. 当发生异常后,catch中的return执行情况与未发生异常时try中return的执行情况完全一样。
  6. finally块的语句在try或catch中的return语句执行之后返回之前执行且finally里的修改语句可能影响也可能不影响try或catch中 return已经确定的返回值,若finally里也有return语句则覆盖try或catch中的return语句直接返回。

使用TWR(Try-with-Resource)

对于实现AutoCloseable接口的资源,Java提供自动close资源的方法。

分析堆栈元素

栈的调用

异常处理机制:异常被抛出时,JVM根据异常中的信息查找处理异常的代码handler,通过调用栈进行反向查找(从栈顶开始),直到找到为止,如果找不到则终止程序。

获取某一异常的堆栈信息

对堆栈信息进行解析。

【参考】
Java finally语句到底是在return之前还是之后执行?

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值