java零基础入门-高级特性篇(九) 异常 中
上一节讲到了检查异常,这种必须处理的异常到底该怎么处理呢?通常的处理方式就是捕获异常或者抛出异常,捕获异常就是在异常出现的时候当场解决,而抛出异常则是把锅甩出去,把异常往上层抛出,让上层逻辑来解决它。处理异常有专门的关键字,java中的异常家族里有以下几种关键字,try、catch、finally、throw、throws,下面来分别介绍它们。
捕获异常
捕获异常就是当场就地正法,使用try和catch关键字来处理异常。try用来监视代码逻辑的运行,如果没有异常,那么程序会一直运行到结束,而一旦发生异常,并且在try的监控范围之内,那么程序就会跳转到catch部分,运行catch里面的代码。如果没有捕获异常,程序会直接结束,所以捕获异常可以给我们一次挽救程序异常停止的机会,就算不能挽救,也至少可以知道为什么程序会出现异常。
try-catch上面这个try-catch结构就是基本的捕获异常结构,try后面的程序就是正常的逻辑代码,catch后面是如果发生了异常需要执行的代码。需要注意的是,在出现异常以后,不会继续执行程序,而是直接跳到catch部分执行代码,所以这里输出完第一个打印语句以后就马上输出了异常信息。
e是Exception的对象,调用Exception父类Throwable的方法printStackTrace(),输出异常信息,输出异常信息有多种方式,printStackTrace()是一种,这种方式输出的是最详细的错误信息,包括出现异常代码的行号,异常信息和异常原因,这种方式适合调试程序,找到错误。还有一种getMessage(),这种输出只会输出异常的信息,比上面一种方法输出的信息要少,这种方式适合记录日志,将错误的信息作为日志记录下来,以便需要的时候排查问题。
多个异常的捕获结构
上面的例子是使用Exception捕获的异常,其实理论上来说,应该使用最准确的异常来捕获,由于Exception是所有异常的父类,所以使用Exception没有问题,但是最适当的方式是使用FileNotFoundException来捕获这里的异常。为什么要用子类来捕获异常?因为使用子类捕获异常可以将异常处理的更加精细,比如下面这个例子。(里面流和反射的知识可能没有学到,但是此处只需关注异常即可)。
使用子类捕获当一段逻辑中有可能出现多个异常需要捕获的时候,如果直接使用Exception,那么只能执行一个异常逻辑,而不能将不同的异常区分开。这样就导致无法根据不同的异常进行不同的异常处理。当一段逻辑中出现多个需要捕获的异常的时候,可以在try后面接多个catch,分别对不同的异常类型进行捕获。
同时使用Exception和Exception的子类在捕获异常的时候是不冲突的,但是子类的捕获必须在父类之前,如果第一个catch的是Exception,那么他会直接捕获所有异常,不能单独处理其他异常了。异常捕获的顺序是按照异常出现的顺序来的,如果首先出现的是文件找不到异常,那么会被FileNotFoundException捕获,如果首先出现的是ClassNotFoundException,那么会被Exception捕获。
有finally的结构
讲完try和catch关键字以后,再来看另一个关键字finally。在处理异常的时候,try关键字是必须出现的,有了try关键字,程序才会在try所包含的代码块中捕获异常,而catch和finally是可以任意出现一个的,也可以两个同时出现。finally的特性是,不论在catch中是否出现异常,finally中的代码都会被执行。因为有一些代码如果写在try中,如果出现异常,那么这些代码是可能不会被执行的,如果写在catch中,如果不发生异常也不会执行,所以需要一个地方来写无论是否出现异常都会被执行的代码。
finallyfinally在处理资源的时候非常有用,比如IO,网络,数据连接等等,因为在使用这些资源的时候需要在代码中手动的回收,但是如果发生异常就不会执行到回收资源的代码,所以在finally中回收资源是一种很好的选择。
使用finally需要注意的几个地方:
1.如果有一个或多个catch关键字的话,finally要出现在最后一个catch之后。顺序如果有错误会发生编译错误。
2.不建议在finally里面使用return关键字。
finally中的return在finally里面是可以使用return关键字的,但是会导致结果与预期不符合。比如上面这个例子,上例中其实是不检查异常,可以捕获也可以不捕获,这里为了说明finally就捕获异常了。这里预期返回的是两个参数的商,程序运行到try中的return是不会马上结束方法的,因为后面有finally语句,而finally语句中也有return,最后的结果就是finally中的return导致try中的return无效。无论程序是否发生异常,方法预期返回的结果都被改变了,返回的不是程序希望得到的两个参数的商,而是一个与参数无关的字符串,所以通常不建议在finally中使用return关键字。
final ,finally 和finalize
这个地方要指出的是,这几个关键字八竿子打不着关系,但是经常会有外行题目问这几个关键字有什么区别。这里简单说一下。
final 定义的变量,初始化变量后不可修改。final定义的方法不可以被覆写。final定义的类不可以继承。
finally用于异常结构,不论是否发生异常,都会运行finally中的代码。
finalize用于定义垃圾回收器应该执行的操作。
抛出异常
捕获异常讲完了,轮到抛出异常了。前面说了检查异常,有没有想过,为什么检查异常就必须处理呢?因为在定义类,方法的时候,源码已经将异常抛出了,所以你在使用类的时候就必须处理它,要么捕获,要么抛出。前面例子中
FileOutputStream out = new FileOutputStream(file);
会有一个FileNotFoundException类型的异常必须处理,来看看FileOutputStream这个类的构造器。
抛出异常什么是抛出异常?
抛出异常就是遇到检查异常,并没有捕获异常直接处理,而是将异常交给调用方处理。
为什么要抛出异常而不是直接捕获?
因为设计上的需要。当我们在写一个业务的时候,碰见异常最好的方法就是捕获并处理它。但是如果写的是一个公共的工具方法或者是父类,抽象类等需要将业务进行抽象的时候,并不能预见到具体的业务是什么,所以不能直接给出解决方案,这时候就需要将异常交给调用方,在使用者具体使用的时候,再来捕获该异常,根据具体情况确定具体的处理方式。
异常具体是怎么抛出的?
异常的抛出首先在一个需要抛出异常的地方将异常往上一级(方法的调用者)抛出,然后上一级还可以继续往上一级抛出,如果到最后都没有被捕获,该异常会被抛给jvm,jvm也没法处理异常只能把异常信息打印出来。
这个过程就像出了问题,开始甩锅一样。方法A出了问题,自己可能没有办法处理,就把锅甩给了方法B,方法B一看这个我也没法解决啊,转手又甩了出去,最后这个锅被甩给了老大哥JVM,JVM老大哥看到异常也只能干瞪眼,没有办法最后只能把异常信息打印出来,谁写的代码谁来认领一下,错误给你看了,自己想办法去解决。
抛出异常java使用关键字 throws 抛出异常,throws后面跟上异常的类型,跟catch的捕获类型差不多,定义什么类型的异常就会抛出什么类型的异常,如果直接抛出Exception,那么就是抛出所有的异常类型。跟catch可以捕获多种异常类型一样,throws也可以抛出多种异常类型,这样就可以让上一级的代码根据不同的异常类型分别进行处理。如果只抛出Exception类型的异常,上一级就无法对异常进行精确的控制了。
抛出多个异常使用throws同时抛出多个异常的时候,使用逗号将多个异常分开。throws这种抛出异常的方式可以看做是一种被动式抛异常,因为throws抛出的异常可能发生也可能不发生,java中除了throws抛出异常,还有一种主动式抛异常,下面来看看什么是主动式抛异常。
throws 和 throw
主动抛出异常的关键字是throw。和throws只差了一个小写字母s,这里需要重点区分开两种异常抛出方式的区别。
throws:1)抛出的是类,在方法后面写的是异常的类名 2)可以同时抛出多种类型异常 3)throws抛出的异常不一定会发生 4)在方法名处抛出
throw:1)抛出的是异常类的实例 2)只能抛出一种异常 3)抛出的异常一定会发生 4)在方法内部抛出
throw用在抛出不检查异常的情况比较多。使用throw可以将代码的逻辑补充的更加完整,因为某些异常在特定的情况是需要根据业务逻辑来判断是否抛出,在特定的情况下是可以确定异常的,而不是像throws不确定是否会出现异常。这种情况下就可以使用throw在方法体中抛出异常。
throw上例中,假设用户需要输入两个数字,然后计算两个数字的商。用户输入是不确定的,但是一旦用户将intTest2输入为0,代码逻辑可以确定这里肯定会有一个异常,那么可以直接使用throw来抛出这个异常。一旦在调用方法时捕获到该异常,也可以确定异常的信息,比如上例中可以将捕获到的信息直接反馈给用户,第二个数不能为0。
需要注意的是throw只是抛出异常的方式比较灵活,可以在代码逻辑中抛出异常,而抛出异常以后,上级的处理逻辑和throws是一样的,要么继续往上级抛异常,要么捕获异常。
网友评论