聊聊关于Java异常的一些理解
1. 异常
含义:异常是在程序中导致程序中断运行的一种指令流。
首先要清楚异常(exception)和错误(error)
Error类和Exception类都是继承Throwable类,
1)Error(错误)是系统中的错误,程序员是不能改变的和处理的(无法用代码处理),是在程序编译时出现的错误,只能通过修改程序才能修正。
一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。
所以,我们应当尽量去避免error的发生。
2)Exception(异常)表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。作为程序员,我们遇到异常一定要尽量根据客户需求去解决这些异常,而不是仅仅让异常打印出来。
(图解,关于Error与Exception的关系图)
发生异常的本质:
实际上是产生了一个该类异常的对象。
Java语言是一门面向对象的编程语言,因此,异常在Java语言中也是作为类的实例的形式出现的。当某一方法中发生错误时,这个方法会创建一个对象,并且把它传递给正在运行的系统,这个对象就是异常对象。
异常的类型:
异常Exception主要分为两大类:
受检异常(Checked Exception)与非受检异常(Unchecked Exception)
1)受检异常
所谓受检异常是指编译器要检查这类异常,编译器要求你必须要对某段代码try…catch… 或者 throws exception抛出异常,也就是说,当我们的代码还没有运行时,编译器就会检查这段代码,对可能出现的异常必须做出相对的处理。
例如编写一个自动关机的指令:
编译器就帮我们检查了可能出现的异常,并要求我们处理
当抛出可能的异常后,不再报错
2)非受检异常
所谓非检查异常是指编译器不会检查这类异常,意思是在代码的编辑编译阶段就不是必须处理,编译器不要求强制处置,我们可以自己尽量避免这类异常的发生。
非受检异常其中包含了运行时异常(Runtime Exception运行时异常是因为程序在执行时,运行环境发现了不能执行的操作),也就是说运行时异常都是编译器不会自己检查的异常。如果一个异常不是Exception的直接子类,而是RuntimeException的子类,则表示程序在操作的时候可以不必使用try…catch进行处理,如果有异常发生,则由JVM进行处理。当然,也可以通过try catch处理。
例如当我们利用Scanner读取时,可能发生输入与要求输入不匹配的情况,我这里希望读入一个int类型的数据,并没有考虑输入字符串或其他类型的情况,但是编译器并没有帮我们指出可能发生的错误。
尽管如此,我们也可以正常执行完这段程序
但是如果输入不匹配int类型的话,就会发生下图异常
我们发现一旦发生异常,在异常出现之后的代码就不会再执行
2.处理程序的异常
异常产生后,如果不做任何处理,程序就会被终止(与房子的地基若是已经发生问题,房子就不能再继续造了,是一个道理)。为了保证程序有效地执行,需要对发生的异常进行相应的处理。
如果要想对异常进行处理,则必须采用标准的处理格式,处理格式语法如下:
try{
// 有可能发生异常的代码段
}catch(异常类型1 对象名1){
// 异常的处理操作
}catch(异常类型2 对象名2){
// 异常的处理操作
} …
finally{
// 异常的统一出口
}
try…catch
try…catch的处理流程:
1、 一旦产生异常,则系统会自动产生一个异常类的实例化对象。
2、 那么,此时如果异常发生在try语句,则会自动找到匹配的catch语句执行,如果没有在try语句中,则会将异常抛出。
3、 所有的catch根据方法的参数匹配异常类的实例化对象,如果匹配成功,则表示由此catch进行处理。 因此,try语句块中存放的是可能发生异常的代码块,catch在try之后,用来激发被捕获的异常。
需要注意的是,在try语句块中一旦有代码产生了异常,之后的代码将不再执行,如果产生的异常与catch要捕获的异常类型相匹配(即括号里存放的异常类型,可以是多态),跳转到catch块中进行处理;catch块执行完毕后,程序后面的代码照常进行。
拿这张图为例,如果我们在第十行代码执行时因为输入有误而产生了InputMismatchException异常,11-12行代码将不再执行,因为异常类型相匹配,直接跳转到catch中,执行其中的代码,然后照常完成执行之后所有的代码。
由此可知,Java的异常处理是结构化的,不会因为一个异常影响整个程序的执行。
涉及多态的使用
异常的种类众多,我们不一定能弄清楚每一个异常的类型,但是我们可以利用所有异常的父类Exception来进行catch,一抓一个准
finally(很重要!)
完整的异常处理语句一定要包含finally语句。
无论程序中有无异常发生,无论try-catch是否执行完毕,finally语句块都会被执行。
看下面的举例:
这里故意不输入int类型数字,第10行发生了异常,11-12行不再执行,即使不进行catch让控制台播报出异常,但finally中的语句块依然会执行。
finally不会被执行的情况:
1)在finally语句块中发生了异常
这里故意输出1/0,因为0不能作为除数,所以finally第14行一定会发生异常,然后来看看代码的运行结果:
另外我们也能看出finally是否执行完毕与try中是否顺利执行完毕无关,finally中一旦发生异常,finally就不能正常执行完毕
2)在前面的代码中使用了System.exit()退出了程序
举个例子:
故意输入错误,让第十行产生异常进入catch并执行System.exit()语句,再看看运行结果:
可以发现finally中的语句并没有执行,因为在执行finally之前程序就终止了。
3)程序所在的线程死亡
4)关闭CPU (电脑关机、重启)
来看一些容易弄错的情况:
我们知道return是给方法返回数值的语句,也是结束这个方法的语句,那么return如果在finally之前,finally还会执行吗?
执行结果:
没错,finally还是执行了。我们可以这么理解:return需要返回给方法一个结果,尽管是void它也需要准备一个没有值的值给到我们的方法,那么在它准备返回的这个过程中,finally趁机执行了。
下面这个例子也一样。
try中没有异常,代码会执行到return这一行,此时的p的年龄属性是18岁,但是return在准备把这个Person p返回时,finally悄无声息地把p的age属性设置成了28,所以return最后返回了28岁的属性,结果如下:
最后看一个需要注意的点:
这串代码的运行结果为:
不是说finally执行了吗,怎么只返回了10?
这里finally确实是执行了,但这里就涉及到数据的备份了,return返回的是备份的数据。与上一个例子相比,Person是一个类,它存储在堆内存中,我们创建的p实例有一个地址指向Person类,被备份的是这个地址,return要返回的也是这个地址,finally修改的是这个地址中存放的p实例,所以p的属性被改变了以后,return的还是p的内容,也就是p被改成什么样return就返回什么样;但是这里的int = 10, 只有这个数字10被备份了,并没有地址指向,不管finally中怎么改(finally本身是已经执行了),return的还是我们备份的数据10,所以我们的编译器也会做出相应提示:这个finally中20并没有用到。
throws关键字
此关键字主要在方法的声明上使用,表示方法中不处理异常,而交给调用处处理。
格式:
返回值 方法名称()throws Exception{
}
我们若是在方法中不处理,可以throws该异常交给调用这个方法的地方去处理,比如可能是main方法中,再用try-catch处理,如果main方法中再用throws抛出异常给JVM,程序就会终止。
throw关键字
throw关键字表示在程序中人为的抛出一个异常,因为从异常处理机制来看,所有的异常一旦产生之后,实际上抛出的就是一个异常类的实例化对象,那么此对象也可以由throw直接抛出。
代码: throw new Exception(“里面提醒异常的内容”) ;
如果有错误或理解不当的地方还请大佬指出!
要是这篇文章有帮助的话,可以留下一个赞嘛~