java编程思想读书笔记----第十二章 通过异常处理错误

  发现异常的理想时期是在编译期,也就是在试图运行代码之前。然而编译期不能找到所有的错误,余下的问题必须在运行期间解决,也就是异常处理。

1、基本异常

  异常情形是指阻止当前方法或者作用域继续执行的问题。无法在当前环境下获得必要的信息来解决问题。当异常出现时,会跳出当前环境,也就是抛出异常。当抛出异常时,几件事会同时发生。首先,会在堆上new一个该异常的对象,然后,当前的执行路劲被终止,并从当前环境弹出对异常对象的引用。此时,异常处理机制接管程序,并寻找一个恰当的地方继续执行程序。这个恰当的地方就是异常处理程序,他将程序从错误状态中恢复,要么强制停止程序,告诉我们错误在哪里,要么强制处理问题,并回归到稳定状态。
1.1、异常参数
  同其他对象一样,我们总是在堆上new一个异常对象。所有标准异常类都有两个构造器:一个是默认构造器;另一个是接受字符串作为参数,能把相关信息放入异常对象的构造器。
  能够抛出任意类型的Throwable对象,它是所有异常的根类。通常,针对不同的异常会抛出不同的对象,可以通过异常类的名称来暗示。通常,异常对象中仅有的信息就是异常类型,除此之外不包含其他有用的类型。

2、捕获异常

guarded region(监控区域)它是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。
2.1、try块
  跟在try关键字后的普通代码块。
2.2、异常处理程序
  紧跟在try块后,用catch关键字表示System.out

3、创建自定义异常

4、异常说明

  告诉客户端程序员该方法可能抛出的异常类型。异常说明属于方法声明的一部分,紧跟在形参列表之后。使用了附加关键字throws。
  代码必须与异常说明保持一致。如果方法里的代码产生了异常却没有处理,编译器会提醒你:要么处理这个异常,要么在异常说明中表明此方法将产生异常。不过可以声明方法将抛出异常,实际却不抛出,这样做的好处是为异常先抢占一个位置。这种编译时被强制检查的异常称为被检查的异常

5、捕获所有异常

  可以通过捕获异常的基类Exception来捕获所有异常,最好将Exception放在处理程序列表的末尾。
  下图展示了Exception中的一些基本方法。


5.1、栈轨迹
  printStackTrace()方法所提供的信息可以通过getStatckTrace()方法来直接访问,这个方法返回一个由栈轨迹中的元素组成的一个数组,其中每一个元素都表示栈中的一帧。元素0是栈顶元素,并且是调用序列中的最后一个方法调用(该Throwable被创建和抛出的地方)。数组中最后一个元素和栈底是调用序列中的第一个方法调用。
  如果只是把当前异常对象重新抛出,那么printStackTrace()方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的异常信息。要想更新这个信息,可以调用fillInStackTrace()方法,它将当前调用栈信息填入原来的那个异常对象建立的。调用fillInStackTrace()方法的那一行就成了异常的新发生地了。
5.2、异常链
  Throwable的子类在构造器中都可以接受一个cause参数。这个cause表示原始异常,通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也可以通过异常链追踪到异常最初的地方。
在Throwable的子类中,只有Error、Exception以及RuntimeException提供了带cause参数的构造器。如果要把其他的异常链接起来,应该使用initCause而不是构造器。

6、java标准异常

  RuntimeException(运行时异常),也被称为不受检查的异常,它会被java虚拟机自动抛出,不用手动捕获。它能给调试带来便利,容易发现编译器无法发现的编程错误。

7、使用finally进行清理

  当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally语句 。finally语句总是会执行,即使是return或异常没有被捕获的情况。
  异常丢失,try块中的异常会被finally中的异常取代,导致丢失。一种更简单的丢失异常的方式是从finally子句中返回。

8、异常的限制

  当覆盖方法时,只能抛出在基类方法的异常说明里列出的那些异常。这限制意味着,当基类使用的代码应用于其派生类对象时,一样能够工作,异常也不例外。例如,在抽象基类中,构造器和event()方法都声明将抛出异常,实际上却没有抛出。这样做可以强制用户去捕获可能在覆盖event()方法后增加的异常。对抽象方法同样适用。如果一个接口A定义了一个在B类中的方法event()和一个不在B类中的方法rainHard()。这两个方法都抛出异常e1。如果类C在拓展了B的同时又实现了A,那么B中的方法event()就不能改变在A中方法的异常接口。否则,在使用基类的时候就无法判断是否捕获了正确的异常。
  另外,当B中存在未声明异常的walk()时,C中的walk()抛出异常会造成编译出错。因为若允许的话,将B的对象替换成C的对象时,B不用处理异常而C却有可能抛出异常。通过强制派生类遵守基类的异常说明,对象的可替换性得到了保证。派生类可以不抛出异常,即使是基类所定义的异常。
  异常限制对构造器不起作用,派生类构造器不能捕获基类构造器的异常。
  异常说明不属于方法类型的一部分,不能基于异常说明重载方法。

9、构造器

  当发生类似与打开文件这样的行为时,只有用户调用了特殊的清理方法才能得以清理,但是如果构造器在执行时失败,对象没有构造完成,此时清理工作是无法进行的,或者说此时对象并未创建成功,不需要清理,或者对象内部有部分需要清理。
  通用的清理惯用法是在创建需要清理的对象后,立即进入try-catch块

10、异常匹配

  抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的 处理程序。且异常类型可以捕获它本身以及所有从它派生出的异常类。
  将“被检查的异常”转换为“不检查的异常”。
  
  
  
e20 前两行中的异常层次结构允许我们添加新的异常,而不强制改变现有的代码。

e21 记住,当抛出异常时,编译器不会创建对象。 这就是为什么一个派生类构造函数不能捕获一个基类构造函数
异常:它不能从异常失败中“恢复”,因为没有base-
类子对象。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值