第九章 异常处理

异常情况会改变正常的流程,导致恶劣的后果,为了减少损失,应该事先充分预料所有可能出现的异常,然后采取以下措施:

首先考虑避免异常,彻底杜绝异常的发生;如果不能完全避免,则尽可能地减少异常的发生的几率。

如果有些异常不可避免,那么应该预先准备好处理异常的措施,从而降低或弥补异常造成的损失,或者恢复正常的流程。

对于某个系统遇到的异常,有些异常单靠系统本身就能处理,有些异常需要系统本身及其它系统共同处理。

对于某个系统遇到的异常,系统本身应该尽可能地处理异常,实在没办法处理,才求助于其它系统处理。

Java系统提供了一套完善的异常处理机制。正确的运用这套机制,有助于提高程序的健壮性。所谓健壮性是指程序在多数情况下能够正常运行,返回预期的结果,如果偶尔遇到异常情况,程序也能采取周到的解救措施。

9.1 Java异常处理机制概述

在处理异常中要考虑的两个问题:

如何表示异常情况

如何控制处理异常的流程

9.1.1 Java异常处理机制的优点

传统的异常处理方式尽管是有效的,但存在以下缺点:

表示异常情况的能力有限,单靠方法的返回值难以表达异常情况包含的所有信息。

异常流程的代码和正常流程的代码混合在一起,影响程序的可读性,容易增加程序结构的复杂性。

随着系统规模的不断扩大,这种处理方式已经成为创建大型可维护项目的障碍。

Java语言按照面向对象的思想来处理异常,使得程序具有更好的可维护性。Java异常处理机制具有以下优点:

把各种不同类型的异常情况进行分类,用Java类表示异常情况,这种类被称为异常类。把异常情况标识成异常类,可以充分发挥类的可扩展性和可重用的优势。

异常流程的代码和正常流程的代码分离,提高了程序的可读性。简化了程序的结构。

可以灵活地处理异常,如果当前方法有能力处理异常,就捕获并处理它,否则只需抛出异常,由方法调用者来处理。

9.1.2 Java虚拟机的方法调用栈

Java虚拟机使用方法调用栈来跟踪每个线程种一系列的方法调用过程。该堆栈中保存了每个调用方法的本地信息(比如方法的局部变量)。每个线程都有独立的方法调用栈。对于方法Java应用程序的主线程,堆栈底部是程序的入口方法main(),当一个方法被调用时,Java虚拟机把描述该方法的栈结构置入栈顶部,位于栈顶的方法为正在执行的方法。

如果方法中的代码块可能抛出异常,有两种处理方法:

在当前方法中通过try-catch语句捕获并处理异常。

在方法的声明处通过throws语句声明抛出异常。

当一个方法正常执行完毕后,Java虚拟机会从调用栈中弹出该方法的栈结构。然后继续处理前一个方法,如果在执行方法的过程中抛出异常,Java虚拟机必须找到能够捕获该异常的catch代码块。(首先查看当前方法中是否存在这样的catch代码块,如果存在就执行该语句,否则,Java虚拟机会从调用栈中弹出该方法,继续到前一个方法中寻找合适的catch代码块。)

当Java虚拟机追溯到调用栈的最底部,如果仍然没有找到处理该异常的代码块,将按照以下步骤执行:

调用异常对象的printStackTrace()方法,打印来自方法调用栈的异常信息。

如果该线程不是主线程,那么终止这个线程,其它线程继续正常运行。如果该线程是主线程(即方法调用栈的最底部是main()方法),那么整个应用程序会被终止。

9.1.3 异常处理对性能的影响

一般来说,在Java程序中使用try-catch语句不会对应用的性能造成很大的影响。仅当异常发生时,Java虚拟机需要进行额外的操作,来定位处理异常的代码块,此时会对性能产生负面影响。如果抛出异常的代码块和捕获异常的代码块位于同一个方法中,那么这种影响要小一些;如果Java虚拟机必须搜索方法的调用栈来寻找异常处理代码块,对性能的影响就比较大了。

所以,应该确保仅仅在程序中可能出现异常的地方使用try-catch语句,此外,应该使异常处理代码块位于适当的层次。如果当前方法具备某种处理异常的能力,就尽量自行处理,不要把自己可以处理的异常推给方法的调用者去处理。

9.2 运用Java异常处理机制

9.2.1 try-catch语句

在Java语言中,用try-catch语句进行异常处理:

try{

可能会出现异常情况的代码

}catch (SQLException e){

处理操作数据库出现的异常

}catch(IOException e){

处理操作输入输出流出现的异常

}

9.2.2 finally语句:任何情况下必须执行的代码

finally代码块能够保证特定的操作总是会执行。它的形式如下:

public void work() throws LeaveEarlyException{

try{

开门

工作8小时

}catch(DiseaseException e){

throw new LeaveEarlyException();

}finally{

关门

}

}

不管try代码块中是否出现异常,都会执行finally代码块。

这种处理方式在某些情况下是可行的,但不值得推荐,因为它有两个缺点:

把与try代码块相关的操作孤立开来,使程序结构松散,可读性差。

影响程序的健壮性。

9.2.3 throws子句:声明可能会出现的异常

如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法的声明处用throws子句来声明抛出异常。

一个方法可能出现多种异常,throws子句允许声明抛出多个异常,例如:

public void method() throws SQLException, IOException{... ...}

异常声明是接口的一部分,在JavaDoc文档中应描述方法可能抛出的异常。

9.2.4 throw语句:抛出异常

throw语句用于抛出异常,有throw语句抛出的对象必须是java.lang.Throwable类或者其子类的实例。

9.2.5 异常处理语句的语法规则

try代码块后面可以有零个或多个catch代码块,还可以有零个或至多一个finally代码块。如果catck代码块和finally代码块同时存在,finally代码块必须在catch代码块后面。

try代码块后面可以只跟finally代码块。

在try代码块中定义的变量的作用域为try代码块,在catch代码块和finally代码块中不能访问该变量。

当try代码块后面有多个catch代码块时,Java虚拟机会把实际抛出异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个代码块,不会再执行其它的catch代码块。

为了简化编程,从JDK7开始,允许在一个catch子句中同时捕获多个不同类型的异常,用|符合进行分隔。

如果一个方法可能出现受检查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误。

判断一个方法可能会出现异常的依据如下:

方法中有throw语句。

调用了其它方法,其它方法用throws子句声明抛出某种异常。

针对前一条语法规则,从JDK7开始,如果在catch子句中捕获的异常被声明为final类型,那么当catch子句中继续抛出该异常时,可以不用在定义方法时用throws子句声明将它抛出。

throw语句后面不允许紧跟其它语句,因为这些语句永远不会被执行。

9.2.6 异常流程的运行过程

finally语句不被执行的唯一情况是:先执行了用于终止程序的System.exit()方法。java.lang.System类的静态方法exit()用于终止当前Java虚拟机进程,Java虚拟机所执行的Java程序也会随之停止。exit()方法的定义如下:

public static void exit(int status)

return 语句用于退出本方法。在执行try或catch代码块中的return语句时,加入有finally代码块,会先执行finally代码块。

funally代码块虽然在return语句之前被执行,但finally代码块不能通过重新该变量赋值的方式来改变return语句的返回值。

建议不要在finally代码块中使用return语句,因为他会导致以下两种潜在的错误:

覆盖try或catch代码块中的return语句。

丢失异常。

9.2.7 跟踪丢失的异常

在JDK7中,Throwable接口中增加了两个已经提供的默认实现的方法

public final void addSuppressed(Throwable exception)

public final Throwable[] getSuppressed()

以上add方法把差点丢失的异常保存起来,get方法返回所保存下来的差点丢失的异常。

9.3 Java异常类

Java中,所有异常类的祖先类为java.lang.Throwable类。它的实例表示具体的异常类型,可以使用throw语句抛出。Throwable类提供了访问异常信息的一些方法,常用的方法如下:

getMessage():返回String类型的异常信息

printStackTrace():打印跟踪方法调用栈而获得的详细异常信息。

Throwable类有两个直接子类:

Error类:表示单靠程序本身无法恢复的严重错误,比如内存空间不足,或者Java虚拟机的方法调用栈溢出。在大多数情况下,遇到这种错误时,建议让程序终止。

Exception类:表示程序本身可以处理的异常,当程序运行时出现这类异常,应该尽可能地处理异常,并且使程序恢复运行,而不应该随意终止程序。

JDK中定义了一些具体的异常:

IOException:操作输入流和输出流是可能出现的异常。

ArithmeticException:数学异常。如把整数除以0,就会出现这种异常。

NullPointerException:空指针异常。当引用变量为null时,试图访问对象的属性或方法,就会出现这种异常。

IndexOutOfBoundsException:下标越界异常。它的子类ArrayIndexOutOfBoundsException表示数组下标越界异常。

ClassCastException:类型转换异常。

IllegalArgumentException:非法参数异常,可用来检查方法的参数是否合法。

9.3.1 运行时异常

RuntimeException类及其子类都被称为运行时异常,这种异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常时,即使没有使用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

由于程序代码不会处理运行时异常,因此当程序在运行时出现了这种异常时,就会导致程序异常终止。

9.3.2 受检查时异常

除了RuntimeException及其子类以为,其它的Exception类及其子类都属于受检查异常。这种异常的特点是Java虚拟机会检查它,也就是说,当程序中可能出现这种异常时,要么使用try-catch语句捕获它,要么使用throws子句声明抛出它,否则编译不会通过。

9.3.3 区分运行时异常和受检查异常

受检查异常表示程序可以处理的异常,如果抛出异常的方法本身不能处理它,那么方法的调用者应该可以处理它,从而使程序恢复运行,不至于终止程序。

运行时异常表示无法让程序恢复运行的异常,导致这种异常的原因通常是执行了错误操作。一旦出现了错误操作,建议终止程序,因此Java虚拟机不检查这种错误。

9.4 用户定义异常

在特定的问题领域,可以通过扩展Exception类或RuntimeException类来创建自定义的异常,异常类包含和异常相关的信息。

9.4.1 异常转译和异常链

9.4.2 处理多样化异常

9.5 异常处理原则

9.5.1 异常只能用于非正常情况

这种处理方式有以下缺点:

滥用异常流程会降低程序的性能

用异常类来表示正常情况,违背了异常处理机制的初衷。

模糊了程序代码的意图,影响可读性。

容易掩饰程序代码中的错误,增加调试的复杂性。

9.5.2 为异常提供说明文档

9.5.3 尽可能地避免异常

许多运行时异常是由于程序代码中的错误引起的,只有修改了程序代码的错误,或者改进了程序的实现方式,就能避免这种错误。

提供状态测试方法。有些异常是由于当对象处于某种状态下,不适合某种操作造成的。

9.5.4 保持异常的原子性

异常的原子性是指当异常发生后,各个对象的状态能够恢复到异常发生前的初始状态。而不至于停留在某个不合理的中间状态。对象的状态是否合理,是由特定问题领域的业务决定的。

保持异常的原子性有以下办法:

先检查方法的参数是否有效,确保当异常发生时还没有改变对象的初始状态。

编写一段恢复代码,由它来解释操作过程中发生的失败,并且使对象状态回滚到初始状态。

在对象的临时副本上进行操作,当操作成功后,把临时副本中的内容复制到原来的对象中。

9.5.5 避免庞大的try代码块

9.5.6 在catch子句中指定具体的异常类型。

9.5.7 不要在catch代码块中忽略被捕获的异常

9.6 记录日志

输出日志的作用:

监视代码中的变量的变化情况,把数据周期性的记录到文件中供其它应用进行统计分析工作。

跟踪代码运行时轨迹,作为日后审计的依据。

承担集成开发环境中的调试器的作用,向文件或控制台打印代码的调试信息。

可以直接使用Java类库中的java.util.logging日志操作包。这个包中主要有4个类:

Logger类:负责生成日志,并能够对日志信息进行分级别筛选,通俗地讲,就是决定什么级别的日志信息应该被输出,什么级别的日志应该被忽略。

Handler类:负责输出日志信息,它有两个子类:ConcoleHandler类(把日志输出到DOS命令行控制台)、和FileHandler类(把日志输出到文件中)

Formatter类:指定日志信息的输出格式。它有两个子类:SimpleFormatter类(常用的日志格式)和XMLFormatter类(表示基于XML的日志格式)

Level类:表示日志的各种级别,它的静态常量表示不同的日志级别。

9.6.1 创建Logger对象及设置日志级别

首先通过Logger类的getLogger(String name)方法获得一个Logger对象

Logger myLogger = Logger.getLogger("myLogger");

在默认情况下,Logger类只输出SEVERE,WARNING, INFO这前三个级别。可以通过Logger类的setLevel()方法设置日志级别。

logger.setLevel("Level.FINE"); // 把日志级别设为FINE

logger.setLevel("Level.WARNING"); // 把日志级别设为WARNING

logger.setLevel("Level.ALL") // 开启所有日志级别

logger.setLevel("Level.OFF") // 关闭所有日志级别

9.6.2 生成日志

Logger类的severe(), warn(), info()方法等分别生成各级级别的日志。

9.6.3 把日志输出到文件

默认情况下,Logger类把日志输出到DOS控制台

9.6.4 设置日志的输出格式

9.7 使用断言

格式:

assert 条件表达式

assert 条件表达式:包含错误信息的表达式

当表达式的值为false时,就会抛出一个AssertError,第二种形式中,第二个表达式会被转换成错误消息的字符串。

当程序运行时,断言在默认情况下是关闭的。

java异常对象引用变量_Java面向对象编程-异常处理相关推荐

  1. java汽车油耗计算_JAVA面向对象编程-试卷B

    JAVA面向对象编程-试卷B 所属分类:其他 开发工具:Java 文件大小:15KB 下载次数:1 上传日期:2017-11-10 20:39:45 上 传 者:弑神唐三 说明:  定义一个交通工具类 ...

  2. java类声明语句_Java面向对象编程-类的声明周期

    第十章 类的生命周期 10.1 Java虚拟机及程序的生命周期 当通过java命令运行一个Java程序时,就启动了一个Java虚拟机进程.Java虚拟机进程从启动到终止的过程,称为Java虚拟机的生命 ...

  3. java为什么使用封装_Java面向对象编程为什么需要封装

    1.概述 学习了前面的面向对象的基础概念后,我们了解要以面向对象的思维来思考一个事物.面向对象有三个基本属性:封装.继承.多态.今天我们就重点讲解其中一个封装的概念. 抛开"封装" ...

  4. java线程本地变量_Java并发编程示例(九):本地线程变量的使用

    这篇文章主要介绍了Java并发编程示例(九):本地线程变量的使用,有时,我们更希望能在线程内单独使用,而不和其他使用同一对象启动的线程共享,Java并发接口提供了一种很清晰的机制来满足此需求,该机制称 ...

  5. java异常类关键字_Java中的异常处理关键字是什么?

    java异常处理中使用了四个关键字. throw:有时我们明确要创建异常对象然后抛出它来停止程序的正常处理.throw关键字用于向运行时抛出异常来处理它. throws:当我们在方法中抛出任何已检查的 ...

  6. 笔记本电脑java版下载教程_Java面向对象编程笔记本 PDF 下载

    主要内容: Ⅰ.语法基础 //导入 java.util 包下的 bai Scanner 类,导入后才能使用它.(放在 public class 之前) //Scanner 类的对象的定义形式 //从键 ...

  7. java泡泡堂教程_JAVA面向对象编程课程设计——泡泡堂(个人博客)

    二.个人负责模块或任务说明 GUI设计 游戏界面类.地图类的实现(实现与Player类的交互) 阿里巴巴扫描纠错 团队博客编写 三.自己的代码提交记录 四.自己负责模块或任务详细说明 1.地图Fiel ...

  8. java面对对象教学_Java面向对象程序设计教与学

    原标题:Java面向对象程序设计教与学 面向对象程序设计(Object Oriented Programming,OOP)主要研究如何从对象的角度出发构建程序单元以及程序开发机制,主要内容包括抽象的技 ...

  9. Java基础【之】面向对象编程(封装、继承(extends、方法重写、super)、多态(动态绑定、重载/重写)、代码实现)

    Java基础[之]面向对象编程(封装.继承.多态.代码实现) 1.封装 2.继承 2.1.extends 2.2.方法重写 2.3.super 3.多态 3.1.对象的多态.方法的多态 3.2.动态绑 ...

最新文章

  1. java B2B2C springmvc mybatis多租户电子商城系统(三):服务提供与调用
  2. linux grep find查找文件夹、代码中的某行/字符串
  3. python相关性分析的散点图怎么做_Python:matplotlib 和 Seaborn 之散点图和相关性 (三十二)...
  4. finalshell连接失败解决方法_Windows 无法连接到SENS的解决方法
  5. 句柄 matlab_matlab 整车仿真
  6. Javascript中NaN、null和undefinded的区别
  7. LINQPad工具-linq、sql、IL优化和转换
  8. 寄存器位域、位操作等示例
  9. 高数叔c语言课件,高数叔网课资源合集 高数上下、线代、模电、物理、复变合集...
  10. 轻便型面部捕捉,一个APP就搞定!
  11. 怎样把ICO图标改成圆形的?
  12. day03 数据预处理
  13. 类似微信5.x朋友圈的弹出框评论功能
  14. tableau大屏bi_Excel,Tableau,Power BI ...您应该使用什么?
  15. python 计算机网络课程设计(网络主机扫描程序)
  16. javaweb JAVA JSP运动会管理系统JSP运动会成绩管理系统 JSP校运会报名信息管理系统
  17. 安装ST LINK驱动后显示成功,但无法识别问题的解决办法之一
  18. 2021.12.20(第二周) 实习周记lzhuan
  19. 河南企服网:语音自动外呼机器人效果好吗?
  20. C语言实现平均学分绩点计算

热门文章

  1. 两个字符串之间的连接函数,不使用strcat()函数
  2. 全部开课!加入学习群一起进步(附点云、多传感器融合、SLAM、三维重建课程)...
  3. 学术期刊因投稿者并非双一流高校作者而拒稿引热议!这算学历歧视吗?
  4. antd Form.Item 中如何获取到Select的label值
  5. SpringBoot在Tomcat下面启动,访问路径
  6. OpenCV | OpenCV将图像转换成黑白图像(二进制)
  7. 数据科学| 蛋白向量分析
  8. Python中常用的一些操作总结(未完待续)update @ 2017-5-18
  9. Linux绝对权限和相对权限法,Linux基础学习笔记
  10. Nature子刊:干旱条件下土壤细菌网络的稳定性不如真菌网络