目录
一、为什么需要异常处理
在生活中我们都希望能够事事如意,但在做事情的时候总是出现这样那样的问题。如:
⚫ 正常上班时间,突然断电了。这时候你会听到哀怨四起:我又没有保存啊!今天上午的劳动又白费了!公司的 电太不给力了。
⚫ 北京市民杨小姐正在逛街,不慎坠入路面坍塌处,被坑内热水管渗出的热水严重烫伤等等情况。 这些现象时有发生,为了减少不必要的损失,应该事先做出充分的准备,然后采取相应的解决措施。这些非正常 的现象就称为异常,而异常处理就是出现异常时采取的一系列的措施。同理程序中也会出现这样那样问题,这就需要 异常处理,使系统能够继续正常运行。
1、程序中的异常
案例1:编写一段代码,当正常收入时,程序可以正常执行完毕,当输入某些特殊值时,程序出现异常并 崩溃掉。
特别输入:3.14,运行结果如下图所示:
这时程序在运行期间就出现了问题,因为我们输入的不是 int 型数字,程序因此中断运行。控制台里显示了问题的描 述信息和发生问题的位置,这就是异常。
特别输入:10 和 0,程序也会发生异常,运行结果如下图所示:
我们都知道除数不能为 0,程序因此中断(崩溃),这也是异常。 通过上面的例子我们可以得出这样一个结论:程 序在运行期间可能会出现各种各样的问题,而且很多问题是无法避免的。但是我们不希望程序因为某个地方出现问题 而导致整个程序都无法运行,这就需要使用 Java 中的异常处理机制来解决。
通过仔细观察,我们发现虽然程序崩溃了两次,但是显示信息并不一样:InputMismatchException 和 ArithmeticException,业就是说这是两种不同类型的异常。
注意:java 中所有的异常类型都以 Exception 单词结尾。
2、什么是异常
异常就是程序在运行过程中所发生的不正常事件,它会中断正在运行的程序。 出现异常并不可怕,关键是想办 法处理异常,要在程序中处理异常,主要考虑两个问题:
2.1、
如何表示异常情况?
2.2、如何控制处理异常的流程?
首先,我们先介绍第一个问题:如何表示异常情况?
假如你开车不小心闯红灯,这时候一个交警向你敬个礼说:你好,你触犯《道路交通安全法》第 26 条:“红灯 表示禁止通行”,依据《道路交通安全法》第 90 条:“处警告或者二十元以上二百元以下罚款”;对你罚款 200 元。 如下图所示。 这就是一个异常,怎么表示这个异常呢?
这个异常有罚款金额 200 元,罚款原因:闯红灯,责任人:司机,处理人:交警张华。
Java 语言是按照面向对象的思想来处理异常,使得程序具有更好的可维护性。Java 异常处理机制具有以下 优点
⚫ 把各种不同类型的异常情况进行分类,用 Java 类来表示异常情况,这种类被称为异常类。把异常情况表示成异 常类,可以充分发挥类的可扩展和可重用的优势。
⚫ 异常流程的代码和正常流程的代码分离,提高了程序的可读性,简化了程序的结构。
⚫ 可以灵活地处理异常,如果当前方法有能力处理异常,就捕获并处理它,否则只需抛出异常,由方法调用者来 处理它。
Java 已经把程序在运行中出现的大部分异常都设计好了,我们只需要直接拿着使用就行了。下图列出了 Java 的 异常体系结构:
Exception 是异常类的祖先,处于继承关系的最顶层。上图只列出了一些常见的异常类,下表介绍了这些常见异 常类的说明:
以上解决了第一个问题: 如何表示异常情况?使用异常类表示异常情况;那么第二个问题: 如何控制处 理异常的流程?这就是异常处理的机制了。
注意:上边提到的异常,在以后的编程中都是比较常见的,刚开始感觉好多,好难记,多看看,时间长了自 然就会了。
3、回顾
大家想一想,我们在 Java 学习和我们企宣项目操作过程中,我们遇到过哪些异常?出现这些异常的原因是什么? 你又是怎么处理的那?请大家踊跃发言。
建议大家可以把自己在平时学习和项目中所遇到的错误、异常、思路、解决方式等都记录在文档、云笔记上, 以后再遇到类似问题也可以有章可循,快速解决。正所谓积土成山,积水成渊,相信通过大家的一点一滴的积累,我 们的技术一定会更上一层楼。
二、异常处理
下面解决第二个问题:异常出现了,怎么控制程序的执行流程问题;这就是异常处理机制。
Java 提供了一套完整的异常处理机制。正确运用这套机制,有助于提供程序的健壮性。所谓程序的健壮性,就是指 程序在正常情况下返回正确结果;如果遇到异常情况,程序也能采取周到的解决措施。相反,不健壮的程序则不能事 先预计到可能会出现的异常,或没有提供强大的异常处理措施,导致程序终止或返回错误结果,很难检测到异常原因。 Java 的异常处理机制主要是通过五个关键字来实现的:
⚫ try:我们通常把可能出现问题的代码放到 try 块中;
⚫ catch:如果 try 块中的代码在执行过程中,真的出现了异常,则 catch 用来捕获这个异常,从而让系统恢复正常 不至于崩溃;如果 try 块中没有出现异常,则 catch 块中的代码,不会被执行。你可以认为 catch 是一个消防部 门;
⚫ finally:try 块或 catch 块的代码执行后,如果存在 finally 块,则执行此块中代码;
⚫ throw:程序员可以利用此关键字来自己抛出一个异常。在某些时候是非常有必要的;
⚫ throws:用在方法前,声明此方法可能会产生某种类型异常,它强制要求方法的调用者必须用 try-catch 来处理。 这些内容真的有些枯燥,还是在代码中来进行理解吧。
1、try-catch 块
try、catch 是 Java 异常处理中最常用的关键字,通常需要配合使用,我们称之为 try-catch 块。在具体使用时, 需要把可能会出现异常的代码放到 try 语句块中,并用 catch 语句块捕捉异常。
案例2:使用 try-catch 块对上边第一个例子进行异常处理,代码如下所示:
在运行第一个例子的时候出现了两个异常:InputMismatchException 和 ArithmeticException,所以在上边例子 中我们使用了两个 try-catch 语句块分别对这两个异常进行了处理,catch 后的小括号中是需要捕捉的异常类型参数。 这时我们在控制台给第一个数输入 10.0 时,运行结果如下图所示:
该运行结果充分表现出了 try-catch 语句块处理异常的作用,这时程序并没有因为异常的出现而中断。如果我们 在控制台输入两个数字 10 和 0,运行结果如下图所示:
从上图中我们可以看到在除数为 0 时,程序没有像前面那样崩溃,而是继续执行。这正是我们想要的结果。当 try 语句块中的代码出现异常时,Java 系统会在运行时抛出一个相应类型的异常对象,然后 catch 语句块对这个对象 进行捕捉。我们在 catch 语句块里输出了自定义异常信息, 但能否输出比如异常出现的位置,都经历了哪些方法 调用等其他相关信息呢?
了解这些信息有利于我们快速定位到异常的产生点。Exception 类自带的方法可以帮我们了解异常的相关信 息,如下图所示:
案例3:修改上边任务的代码,显示异常的相关信息。
这时当我们在控制台输入两个数字 10 和 0,运行结果如下图所示:
以上是对 try-catch 语句块基本用法的讲解,在实际应用中 try-catch 语句块分三种情况,下面我们分别进行讲 解。
1) try 语句块未出现异常 如果 try 语句块中的代码在运行时没有发生异常,那么 catch 语句块中的所有语句将被忽略,不会执行。如下代 码所示:
这时当我们在控制台输入两个数字 10 和 2,运行结果如下图所示:
2) try 语句块出现异常 如果 try 语句块的代码在运行时出现异常,那么在 try 语句块中,出现异常的那行代码之后的代码将不会执行, 而 catch 块中的语句都会执行。如下示例所示:
这时当我们在控制台输入两个数字 10 和 0,运行结果如图下图所示:
3) try 语句块出现异常,catch 不匹配 如果 try 语句块的代码在运行时出现了异常,但是这个异常与 catch 中捕捉的异常类型不匹配,这时程序就中断 (崩溃)了:
这时当我们在控制台输入两个数字 10 和 0,运行结果如下图所示:
从执行结果可以看出,最后一句“System.out.println("程序执行完毕!");”并没有执行;程序直接中断执行。 就像你闯红灯最重的惩罚是 200 元和扣 6 分,结果判你无期徒刑,肯定要终止手头的工作来处理这个事情。
2、finally 语句块
由于异常会强制中断正常流程,这会使得某些不管任何情况下都必须执行的步骤被忽略,从而影响程序的健壮性。 例如小丽去开水房接开水,正常的流程为:打开水龙头、接开水、关闭水龙头。出现的异常流程为:小丽在接水时突 然头疼晕倒了。以下 getWater() 方法表示小丽的接水行为。
假如小丽突然晕倒,那么流程跳转到 catch 代码块,这样意味着关闭水龙头将不被执行,这样的流程显然是不 安全的,必须要保证关闭水龙头在任何情况下都要被执行。
在应用程序中,应该确保占用的资源被释放,比如及时关闭打开的文件,关闭数据库连接等等。Finally 代码块 能保证特定的操作总会被执行。放在该语句块中的代码不管 try 语句是否出现异常都会被执行,特别是当 try-catch 语句块中有 return 语句时,finally 块还是会被执行。
案例4:finally 的使用。代码如下:
上述代码的运行结果如下图所示:
运行结果表明 return 语句阻止了最后一行代码的执行,但没能阻止 finally 语句块的执行。return 语句会在 finally 语句块之后执行。
3、多重 catch 块
可能出现两种异常,因而当时我们使用了两个 try-catch 块进行异常处理,其实只需要一个 就够了,但是这时必须使用多重 catch 块。
案例5:使用多重 catch 块优化任务 2-1,实例如下所示:
优化后的代码要比之前的代码更简洁。在使用多重 catch 块时有几个问题需要注意:
1、当 try 语句块出现异常时,Java 系统会自上而下分别对每个 catch 块进行异常类型匹配,并执行第一个与异常 类型匹配的 catch 块,这个 catch 块一旦执行,其后的所有 catch 块都会被忽略。
2、多重 catch 块正常的编写顺序是先子类后父类,最后一个一般都是顶层的 Exception 类。参考代码如下所示:
4、throws
如果一个方法可能会出现异常,但没有能力或者本身不愿意处理这个异常,可以在方法声明处用 throws 来声明 抛出异常。例如汽车在运行时可能会出现故障,汽车本身没有办法处理这个故障,因此 Car 类的 run() 方法声明抛 出 CarWrongException(此异常为我们自定义,java API 中并没有)。
这就是 throws 关键字的用法,需要特别注意的是:如果进行了声明,但调用者没有进行异常处理,是无法编译 通过的。如下图所示:
我们给 setSex()方法使用 throws 关键字声明了异常,然后在 main 方法中调用 setSex 方法时出现了编译错误, 这时就必须使用 try-catch 进行异常处理,否则就会一直提示编译错误。throws 关键字后面可以同时声明多个异常, 中间用逗号隔开。
5、throw
前面我们所遇到的异常都是由 Java 系统在运行时自动抛出的,而有的情况下需要程序开发人员自行抛出异常。 这时可以通过 Java 异常处理体系中的 throw 关键字来解决。示例如下:
案例6:对 Student 类中的 setSex 方法进行验证,要求传入值必须是男或女,否则抛出一个异常。
在 setSex()方法中,我们认为如果性别不是男也不是女那就是异常情况,但是 Java 系统无法识别并抛出这个异 常,所以就使用 throw 关键字手动抛出了一个异常对象。上述代码的运行结果如下图所示:
6、异常处理语句的语法规则
异常处理语句主要涉及到 try、catch、finally、throws 和 throw 关键字,要正确使用它们,就必须遵守必要 的语法规则。
1) try 代码块不能脱离 catch 代码块或 finally 代码块而单独存在。try 代码块后面至少有一个 catch 代码块或 finally 代码块;否则编译出错,如图所示:
2) try 代码块后面可以零个或多个 catch 代码块,还可以有零个或至多一个 finally代码块。如果catch 代码块和 finally 代码块并存,finally 代码块必须在 catch 代码块后面。
3) try 代码块后面可以只跟 finally 代码块。如示下例所示:
4) 在 try 代码块中定义的变量的作用域为 try 代码块,在 catch 代码块和 finally 代码块不能访问该变量。如下图所 示:
5) 当 try 代码块后面有多个 catch 代码块时,Java 虚拟机会把实际抛出的异常对象依次和各个 catch 代码块声明的 异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个 catch 代码块,而不会再执行其他 的 catch 代码块。在以下代码块中文件读写的语句会抛出 FileNotFoundException 异常,FileNotFoundException 类是 IOException 类的子类,而 IOException 类是 Exception 的子类。Java 系统把 FileNotFoundException 对象于 IOException 类匹配,因此当出现 FileNotFoundException 时,程序的打印结果为“文件读写错误”。
6) 如果一个方法可能出现检查异常,要么用 try…catch 语句捕获,要么用 throws 字句声明将其抛出,否则会导致 编译错误。如下图所示:
使用 try…catch 语句处理异常:
使用 throws 语句声明抛出异常:
7) throw 语句后面不允许紧跟其他的语句,因为这些语句永远不会被执行:
三、异常应用场景
学习了这么多关于异常的知识,你肯定想问问:我在平时编码时,是不是要有意识地去使用 try-catch 呢? 在这里,我给大家列举两种使用 try-catch 最多的场景:
1) 类型的转换。还记得字符串转日期么?
2) 和外设(硬盘、网卡等)的交互。比如:访问硬盘或网络上的文件,访问硬盘上的数据库等。
在其他时候,你只需要正常编码就可以了,当遇到除法运算时,我们最好在除之前先用 if 判断下除数是否为 0, 也就是说,能预估到的异常,先设法进行避免。随意的使用 try-catch 会造成程序性能的下降,哪怕只是"一丢丢"。