之前学习文件流和数据库操作时大家经常会看到我按照eclipse的提示在main函数后面加上类似throws xxException这样的语句,否则程序会提示错误。就像这样:
或是用try...catch包住一些语句,像这样:
当时我让大家先不用管,以后再说。其实这种语句就叫做java的异常处理,也是我们这篇要探讨的东西。
先说什么是异常。从字面上理解,异常就是不正常的事呗。不光java,生活中也有很多不正常的事。比如你去KTV唱歌,正准备喊麦,结果麦没声了。这是个异常。你换了个麦,又正准备喊麦,结果电视屏幕挂了,没影了。这也是异常。之后你急了,要喊服务员过来修,结果发现按钮不管用。这还是异常。最后你怒了,直接吵着闹着要换房间,这次都没问题了,但你发现你唱跑调了。这些都是异常。
一个java程序也一样,你执行的时候可能会因为种种原因报错,有些错误并不是因为你的语法错误造成的,而是某些外部的因素导致的,这些错误就是异常。按照异常的特点,我们把异常分成三类 - 检查性异常,运行时异常,还有错误。
先说第一种,检查性异常。刚才KTV这个例子中除了最后你唱不上去,其它几个异常其实压根与你无关,麦没声了电视没影了能赖你吗?不能赖。一个java程序也是如此,有些时候并不是你的程序出了bug,而是环境或资源出了bug,这种就叫做检查性异常。之前介绍文件流时抛的几个异常就属于此例,再看一遍:
这两句代码之前都讲过,意思是依照给定的文件创建一个文件流。你的程序本身并没有任何问题,但如果构建文件对象时该文件或文件路径并不存在,那就要抛出异常了。同理还有之前演示过的数据库操作,如果数据库或数据库中某个表不存在也会报错。所以说这种情况是你无法控制的,代码写得再对资源文件出错了也白搭。而且你会发现eclipse的检查机制会用红波浪线提示你此处必须要做出处理,否则编译的时候会出错,这种预判也是检查性异常的优点。java创造者也强制开发人员在类似文件流数据库这种潜在抛出检查性异常的地方对异常进行处理。其实,只要你能确保资源或环境的正确,检查性异常是可以避免的。
第二种叫运行时异常。检查性异常是在编译时就会提示,而运行时顾名思义,只在程序编译完执行时才会提示。这种异常的出现是因为你的程序真的有逻辑上的bug。因为你跑掉了导致唱得不好听,这个怪不得别人。一个典型例子就是数组越界:
arr数组只有三个位置,你非要访问第四个(因为数组从0开始),这不就是逻辑错误么?程序本身并不显示红线,意味着可以编译。类似的逻辑错误还有除以0:
任何数除以0都会得到无穷大的一个数,程序无法给出结果,还是属于逻辑上的问题。这种情况下把逻辑错误修好,运行时异常也是可以避免的。就好比你好好练练,下次唱的时候不跑调就行了。
第三种就叫错误。这种异常最厉害,因为它无法避免,而且不易预判。就好比你在KTV连续唱好几个小时,你势必口干舌燥嗓子冒烟,后边唱的质量肯定有所下降。这种情况既不赖人家也不赖你,实在是自然规律。java中因内存耗尽而抛出的异常就属于这个范畴,你不幸遇上了只能慢慢优化你的代码,或是仔细查验别的可能的情况,没别的好办法。不过错误这种异常毕竟还是少数,我们这篇文章主要还是讨论怎么解决前两种。
捕获异常的方法有两种,第一种是在发生异常的地方直接捕获,用到的是try...catch语句块。用try包住可能会发生异常的地方,用catch输出异常信息用于排错。比如再看上图这个文件流的例子。当我点击程序旁边那个小红叉时,eclipse会提示我选择try...catch还是add throws declaration:
选择try...catch你会发现可能出现异常的地方自动被try包住,catch里面出现一句e.printStackTrace():
e.printStackTrace()就是打印异常信息的意思,参数e的类型是FileNotFoundException。打开java文档查一下它,我们会看到如下信息:
FileNotFoundException中文翻译过来就是“文件没找到异常”,属于文件流异常中的一种。它是一种异常类,从IOException类继承来的。介绍文件流的时候说过IO是什么意思,输入输出流呗。文件流本身又属于输入输出流的一种,所以它抛出的异常继承IOException合情合理,好理解吧。而且介绍继承时说继承的本质就是青出于蓝胜于蓝的过程,所以FileNotFoundException拥有比IOException更详细的信息。
我们再看一下IOException类和所有继承它的类,会发现除了FileNotFoundException,其实还有好几种文件流异常,比如文件系统异常,文件流末尾异常等等。截图不清楚,大家仔细看一下文档中标注的几个地方:
再往上看一级,IOException类继承的是Exception类:
Exception类底下有众多子类,因为它不光包含IO流异常,而是所有的检查性异常和运行时异常,但不包括错误。最后再往上看一级,Exception继承的是Throwable类:
Throwable类是所有异常类的最高父类,它只有两个子类:Exception和Error,Error就是错误。前边说了,Error比较棘手,相对也少见。我们还是主要把注意力放在前两种上,也就是Exception类中的内容。有了这些基础我们再翻回去看刚才的例子,eclipse自动检测出可能抛出的异常是FileNotFoundException,于是在catch语句块中创建出该类的实例e用来捕获该异常。程序执行时,如果try语句块中的程序执行无误,没有抛出异常,则catch语句块不被执行。如果一旦抛出异常,则立即执行catch中的语句,也就是e.printStatckTrace()。由于Exception类中打印异常信息的方法是printStatckTrace(),所以继承它的IOException以及FileNotFoundException类也都有这个方法,直接用就可以。
注意,如果一旦抛出异常,则立即执行catch中的语句。也就是说,只要你try块中抛出一个异常,那剩下的代码就不执行了。比如:
指定文件确实不存在,在FileReader fr = new FileReader(f)处抛出FileNotFoundException异常,于是不再继续执行try块剩余的代码,而是直接执行catch中的e.printStatckTrace()打印异常信息。
有人说我不用eclipse,我的编辑器没那么聪明,不能自动告诉我潜在抛出哪种异常怎么办呢?那么多种异常我该选哪个呢?这种情况下最好的办法就是用父类Exception,直接写Exception e:
它是父类,包含一切种类的异常,打印出的异常信息自然会告诉你属于哪种异常,写程序时你不用管它。
刚才说了,抛异常后就不再继续执行try块中剩下的语句,但如果你偏偏想执行那些语句怎么办呢?还是讲文件流的时候,我说一个文件流打开了用完了最后一定要调用close()方法关闭,如果不关闭就会浪费资源。可是,我不能把关闭过程放在try语句里,否则一旦抛异常就不会被关闭了:
所以,java开发人员又加入了第三个语句块 - finally。一般情况下,不管try块有没有异常,finally块里的所有代码一定会被执行。我们可以把关闭过程放在finally里:
文件流比较讨厌,调用close时也会有检查性异常,类型是IOException,所以finally块里又有一个小的try...catch。我知道麻烦,可没办法。
我刚才说一般情况下,不管try块有没有异常,finally块里的所有代码一定会被执行。那什么是不一般的情况呢?看下面两例,第一个:
第二个:
大家试一下会发现划出来的语句都不会执行,这两个就是特殊情况。第一个例子中finally块里边的代码也会发生异常,是个运行时异常。出了异常怎么还能再执行后边的语句呢对吧?第二个例子System.exit(0)的意思是让程序退出。都退出了当然就不会再继续执行后面的语句了。所以,这两种情况下finally里边的内容不会执行。但一般情况下很少有人这么写,谁愿意没事自己给自己找麻烦呢,对吧?
以上是在可能发生异常的地方直接捕获。还一种方法是把异常抛回给调用者捕获,看下面例子:
当eclipse提示我选择try...catch还是add throws declaration时,我选择了add throws declaration,程序会直接在readFile()方法名边上加上"throws IOException",意思是不自己处理,而是向上抛回给调用者。因为调用它的是对象et,所以我们要对et.readFile()加上try...catch语句块。当然,如果你用的不是eclipse可能没有自动提示,你就在方法名旁边直接添加"throws Exception"就行。这就是抛回给调用者的方法,虽然异常会出现在readFile()方法里,但是我们没在它里面直接捕获,而是让它抛出来。那如果et也不想捕获呢?它继续往上抛:
它也不处理,那就会让更高级别的来处理。咱们这个例子里已经到了main()函数了,再往上抛就会交给最高级的java虚拟机来处理。这么做有一个好处,你会发现我们全程不用写try...catch语句块了。但是,弊端也有,如果不停上抛会造成整个程序运行效率偏低,而且出错后终端会打印出来上抛过程中经过的所有类/对象/方法,排错费点时间。所以,业内大多数人仍然推荐使用第一种方法捕获。
这篇文章的源代码是Exception和Exception2。
本篇知识点及注意事项:
1. 异常分三种: 检查性异常,运行时异常,错误。通常捕获前两种。
2. 捕获异常的方法有两种,第一种是在发生异常的地方直接捕获,第二种是把异常抛回给调用者捕获。
网友评论