您的位置 首页 java

“全栈2019”Java异常第五章:一定会被执行的finally代码块

难度

初级

学习时间

30分钟

适合人群

零基础

开发语言

Java

开发环境

  • JDK v11
  • IntelliJ IDEA v2018.3

友情提示

  • 本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
  • 本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!

1.清理工

大家有没有注意到上面这个标题:“清理工”。

为什么取这样一个标题?

因为我们本章内容主角 finally 就是一名“清理工”。

既然是清理工,那么他的工作肯定跟清理相关了。下面我们就通过实际开发中的一个案例来说说finally是如何成为一名“清理工”的。

实际案例

在实际开发中,我们会有读取文本的需求。

估计有同学想问:文本是啥?

文本就是文本文件,诸如“txt”文件等等。

读取像“txt”文件的需求很常见,也是作为一名合格的开发人员必备操作。下面,我们就来演示怎么读取一个文本。

在开始演示之前,我们需要一个文本文件,所以,请大家跟随我的操作一起,创建出一个文本文件。

提示: 本教学使用的开发工具是 IntelliJ IDEA ,没有安装或想学习怎么使用的同学请点击下面链接根据自己的需求自取。这里不再赘述如何使用 IntelliJ IDEA ,请大家谅解,谢谢!如果大家还有疑问,请在下方评论留言。

首先,请大家使用 IntelliJ IDEA 打开你们的项目:

有可能你的项目不叫“Hello”:

也有可能你的src目录下面没有lab包和main包:

这些都没有关系,没有任何影响。

接下来,请大家在 src目录上右键,然后依次选择“New”->“File”

在file name输入框中写上 demo .txt” ,然后点击 “OK”

创建“demo.txt”文本文件成功:

接着,我们在右侧“demo.txt”文本文件中输入一些字:

我们写了一些“abc”。

接下来,请大家书写一个Main类,它里面有一个主入口方法(main()方法):

如果可以的话,请大家像我一样,把Main类放入main包中,你可以没有lab包(这个是我平时给你们写例子用的包,所以你们不必创建这个包,谢谢你们的厚爱❤️)。

Main类代码我还是贴出来给大家:

好了,继续我们的旅程, 请大家选中“demo.txt”文件,然后右键它,选择“Copy Path”

这一步操作啥意思?

是“复制文件完整路径到剪切板”的意思。

为什么要复制“demo.txt”文件的完整路径到剪切板?

待会我要读取它里面的内容,既然要读取它里面的内容,那么我就 必须要获取到目标文件的路径 ,也就是要知道目标文件在哪,我才能去读它里面的内容,如果我都不知道我要读取的文件在哪,还读什么东西?所以,这一步必不可少。

接下来,我们继续,请大家书写Main类:

这里,大家可能要问了,FileReader是干什么的?

FileReader是一个普通的Java类,和我们之前学习中写过的Animal类、Student类一样,是一个普通的Java类,只不过它里面有它自己的功能,它能做什么呢? 它专门用来读取文件的字符流

这里大家又快要被绕晕了,什么是字符流?

这里我必须要喊一声“停”!就此打住,我们不要再去纠结FileReader具体是什么,它能干什么,因为后面还有 《“全栈2019”Java IO》系列教学文章 在等着为大家解答,我们本章内容重点不是FileReader。所以,这里大家只需知道用 FileReader对象就可以来读取文本文件内容 就行了。

这里有一个细节,请大家一定要注意:

那就是这个 双引号

为什么这个双引号被称之为细节呢?

因为“先有双引号再粘贴内容”和“先粘贴内容再有双引号”有很大区别。

区别在哪呢?

区别就在于,“先有双引号再粘贴内容”:后粘贴的内容中如果存在 转义字符 ,则自动将转义字符进行转义。而“先粘贴内容再有双引号”:先粘贴内容,再将内容用双引号引起来后,引号中的内容如果存在转义字符,则不会自动将转义字符进行转义。

来个例子感受一下,Windows同学可能你复制的路径是这样的:

先有双引号再粘贴内容

大家看到了什么,是不是“\”变成了“\\”,这是转义的效果( IntelliJ IDEA编辑器它帮你完成了 ),因为“\”在一些操作系统平台上或编程语言里需要转义才能被正确识别。

先粘贴内容再有双引号

这里的“\”没被转义。所以这样写有可能报错。

好了,大家,请各位将之前复制的路径粘贴至双引号中,这是我的文件路径:

粘贴好了以后,运行程序,执行结果:

错误信息:

文字版:

/Users/admin/Workspace/Java/Hello/src/main/Main.java

Error:(14, 33) java: 未报告的异常错误java.io.FileNotFoundException; 必须对其进行捕获或声明以便抛出

从运行结果来看,我们的程序好像遇到了一点异常,各位小伙伴们,遇到异常该怎么办?前面刚刚学过如何处理异常,这里我们来处理一下,这里报错说我们遇到了一个FileNotFoundException异常(找不到文件异常),因为在读取文本文件过程中还会报出其他异常,所以大家,我在这里直接捕获所有异常(原因是这里会捕获多个异常,而捕获多个异常这个知识点是在后面章节中讲解,这里没讲到,所以就直接捕获所有异常Exception,请大家谅解):

经过我们对异常的一番处理过后,再运行程序,执行结果:

什么都没有,这是好事,说明我们程序写的没有问题。

好,接下来该是读取文本文件里面的内容了?

使用FileReader类的read()方法来读取文本文件里面的内容:

运行程序,执行结果:

从运行结果来看,“97”是什么东西?我“demo.txt”里面只有“abc”,也没写97这个数字啊,哪来的?

各位,我们FileReader对象的read()方法返回的是字符对应的 ASCII 码值,小写字母“a”对应的ASCII码值就是97。

我们再调用一次read()方法就是继续往下读取一个字符:

运行程序,执行结果:

这次我们读取的是“98”,对应ASCII码表中的小写字母“b”。

我们再调用一次read()方法就是还往下读:

运行程序,执行结果:

这次我们读取的是“99”,对应ASCII码表中的小写字母“c”。到这里,我们“demo.txt”文本文件里面的内容已经全部读取完毕。

但是,我们还想继续往下读的试试,看会出现什么,再调用一次read()方法继续往下读:

运行程序,执行结果:

这里大家看到,打印了一个“-1”,这表示文本中的内容已经读完。文本已经表示读完,还能继续调用read()方法继续往下读取吗?接着试试:

运行程序,执行结果:

结果还是“-1”,所以当文本文件内容被读取完毕之后再读取就是“-1”。

咦,请问大家:我们的程序可不可以再优化一下?这里面有好多重复代码。

优化还是可以的,请大家看一个完整的读取文本文件内容的代码:

我们将其用while循环改写上述程序,循环条件为true,也就是无限循环。通过循环中判断字符码值是否为“-1”来结束循环。这样就循环将文本文件中所有内容全部读取出来了。

运行程序,执行结果:

读,我们也读了,但是打印个ASCII码值实在是不好看,也不太读得懂,还得去查ASCII码表才知道是什么意思。有没有一种方法,能让我直接看到内容本来面目,“a”就是“a”,不是什么97。

办法是有的,那就是将码值类型转换一下,强转成char类型:

运行程序,执行结果:

这样就好多了。皆大欢喜,但是,有个不幸的消息要告诉大家, 执行文件操作,必须要有个关流的操作。

关流?

对,关流就是关闭IO流,FileReader属于IO流的一种,所以它也得被关闭。

怎么关闭?

调用其close()方法。

close()方法什么作用?

关闭流并释放与其关联的所有系统资源。流关闭后,再次调用read()方法,ready()方法,mark()方法,reset()方法或skip()方法将抛出IOException。

关闭以前关闭过的流无效。

关流操作必须要做!

完善上述程序:

这样的程序真的完善吗?

不见得,请看,我给程序制造了一个麻烦:

我们在关流操作前产生了一个异常,请问关流操作执行到了吗?

肯定没有执行到,前面我们也学到过,产生异常后,只有看catch捕获到该异常没有,如果捕获到了,则执行catch子句。

来运行程序,看看执行结果:

从运行结果来看,程序执行了catch子句。

结果中前面的“abc”是打印文本文件内容,后面的“程序发生异常!”是catch子句:

好,这里就遇到了棘手的问题:关流操作是一定要做的,不做不行!如果try代码块里面在关流操作前不产生异常还好,可以顺利执行关流操作;一旦在关流操作之前产生异常,那么就一定执行不到关流操作。

请问这个问题有解吗?

有。那就是我们今天的主角 “finally” 。前面说了这么多就是为了铺垫这哥们的重要性。

2.finally的定义

先来看看finally的定义:

finally代码块是在try代码块退出时必须执行的代码块。

什么意思咱们先不着急理解,待会会解释。先来看看try-catch-finally代码格式:

从格式来看,finally代码块是写在catch代码块的后面,而且它是一定会被执行的。这一点正好是我们想要的,于是,我们用finally代码块来改写上述程序:

好,看似很完美,运行程序,执行结果:

错误信息:

文字版:

/Users/admin/Workspace/Java/Hello/src/main/Main.java

Error:(39, 13) java: 可能尚未初始化变量fileReader

Error:(39, 29) java: 未报告的异常错误java.io.IOException; 必须对其进行捕获或声明以便抛出

哇哦,怎么还抛出异常了?

不急,慢慢来分析,根据错误信息显示,我们程序的39行的fileReader变量可能没有初始化。好,针对这个问题,我给出的解决方案是判断fileReader变量是否为null:

接下来,程序的39行还抛出了一个java.io.IOException异常,我们将其捕获:

好了,我们再次运行程序吧,看看执行结果:

咦,有人就想问,你的流真的关闭了吗?有人不信,我们就在程序中添加一段验证流已经被关闭的代码:

这段添加的代码内容是:

我们在流已经被关闭的情况下,还去调用read()方法,前面说过,在流被关闭的情况下再去调用read()方法,会产生一个IOException异常,于是呢,我们将其捕获,处理程序也很简单,就打印一句话“发生异常,说明我们流已经正确关闭了: ”加上错误信息(e.getMessage()方法调用的结果)。

运行程序,执行结果:

大家看到了吗,流已经被关闭了,其中打印信息中英文“Stream closed”翻译过来就是流被关闭了。

这样一来,我们的程序就完整且完善很多。

对于finally为什么会是“清理工”?

首先,finally自身是没有清理的功能,其次是finally的大多数作用是我们用来做一些清理工作的地方,比如关流。

各位,实际开发的例子我们讲解到此先告一段落,文件的操作后续还有更多文章教学。

前面我们已经抛砖引玉将其finally引出来,finally的定义我们也已经看到了,但是没有解释,下面我们会逐步解释fianlly的定义。

在大家继续继续往下读之前,我先为大家介绍下面小节的内容:

第3小节:主要介绍finally在try代码块中没有发生异常时的情况。

第4小节:主要介绍finally在try代码块中发生异常,catch捕获到该异常时的情况。

第5小节:主要介绍finally在try代码块中发生异常,catch没有捕获到该异常时的情况。

提示:

  • 这3个小节的例子是全新的(例子很小,也很简单)。
  • 在这3个小节中会穿插解释finally的定义,请大家务必联系起来阅读。
  • 如果你觉得自己已经掌握finally关键字,则不必再往下阅读。

3.try代码块中没有发生异常

来一个小例子:

运行程序,执行结果:

从运行结果来看,程序正常执行,没有问题。我们来看看程序代码。

首先,我们在try代码块里面定义了一个变量x:

该变量的表达式是正常的,并没有引发异常,所以程序继续往下执行:

执行结果:

此时,我们的try代码块里面的代码执行完毕:

因为catch没有捕获到任何异常,所以它也不会执行,那么我们的程序是不是就此执行完毕了呢?

还没有,从何处可以看出程序还没有执行完毕?从输出结果:

我们发现finally代码块也被执行了:

由此可见,finally代码块在try代码块中正常执行完退出之后执行。

什么叫try代码块正常执行完毕?

就是try代码块里面代码都执行完毕了,没有发生任何异常。

好,这个例子让我们知道了, finally代码块是在try代码块正常执行完毕后执行。

4.try代码块中发生异常

上一小节,演示了当try代码块中没有发生异常时,fianlly执行时机。接下来我们要演示的是当try代码块中发生异常时,finally执行时机。

来,看例子:

各位,乍一看,好像和上一个例子没有什么区别啊,不,区别在于上一小节例子中的变量x是1除以1,本例子变量x是1除以0,故意制造异常:

运行程序,执行结果:

从运行结果来看,和上一小节不同,不同点在于本例子中catch代码块被执行了。好,我们来看看程序代码。

首先,我们依然在try代码块里面定义了变量x,只不过变量x的表达式是1除以0,这里故意制造了一个异常:

当程序执行到1除以0时,就会抛出一个算术异常,该异常被我们catch捕获到了:

于是呢,就执行了catch代码块,执行结果:

此时,程序还没完,我们finally代码块还没执行呢,从何处能看出finally代码块需要被执行呢?从程序运行结果中:

由此可见, 我们又得知finally在try遇到异常退出时,在正确匹配到异常的catch代码块执行完毕之后执行。

这里有两点需要解释,第一点,什么叫try遇到异常退出?

就是try代码块中出现了异常,此时try代码块不再继续往下执行,而是将这个异常抛出,再由catch子句逐个去匹配异常类型,如果捕获到了,就执行正确捕获到异常的catch子句,如果没有捕获到异常则不执行catch子句。就像这里的算术异常ArithmeticException,我们的catch子句里面参数类型也是ArithmeticException,所以正好匹配成功。

第二点,什么叫“正确匹配到异常的catch代码块”?

因为我们在try代码块里面不会只发生一种异常,有可能发生好几种异常,而且这些异常有可能是相同类型的,也有可能是不相同类型的。所以,我们catch在捕获异常的时候,只会捕获该捕获的异常,什么是该捕获的异常?就是catch子句里面写的异常类型。

5.try代码块中发生异常,但catch未捕获到

当我们try代码块中发生异常,但是呢,catch并没有捕获到,finally代码块会执行吗?

来看看例子。

这里呢,我们catch捕获的是数组下标越界异常ArrayIndexOutOfBoundsException。

这个异常肯定捕获不到,因为我们故意制造的异常是算术异常ArithmeticException,于是呢,catch子句肯定不会被执行。

运行程序,执行结果:

错误信息:

文字版:

我是finally代码块

Exception in thread “main” java.lang.ArithmeticException: / by zero

at main.Main.main(Main.java:13)

从运行结果来看,程序如期发生了算术异常,这个我们就不再述说。主要是看,我们的fianlly代码块执行了没有?

从结果来看,finally代码块执行了。

由此可见,fianlly在try代码块即使遇到异常退出,catch也没有捕获到该异常的情况下,依然会执行。

总结

  • finally代码块是在try代码块退出时必须执行的代码块。
  • finally代码块是在try代码块正常执行完毕后执行。
  • 我们又得知finally在try遇到异常退出时,在正确匹配到异常的catch代码块执行完毕之后执行。
  • fianlly在try代码块即使遇到异常退出,catch也没有捕获到该异常的情况下,依然会执行。

至此,Java中finally代码块相关内容讲解先告一段落,更多内容请持续关注。

答疑

如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。

上一章

下一章

“全栈2019”Java异常第六章:finally代码块作用域详解

学习小组

加入同步学习小组,共同交流与进步。

  • 方式一:关注头条号Gorhaf,私信“Java学习小组”。
  • 方式二:关注公众号Gorhaf,回复“Java学习小组”。

全栈工程师学习计划

关注我们,加入“全栈工程师学习计划”。

版权声明

原创不易,未经允许不得转载!

文章来源:智云一二三科技

文章标题:“全栈2019”Java异常第五章:一定会被执行的finally代码块

文章地址:https://www.zhihuclub.com/199162.shtml

关于作者: 智云科技

热门文章

发表回复

您的电子邮箱地址不会被公开。

网站地图