Java基础系列9: 异常体系
本文是作者的读书笔记和心得整理,部分内容来源于网络,如有侵权,请联系作者。
为什么要使用异常
核心用法是精准定位出现错误的地方,以及把处理错误的代码和正常逻辑代码进行分割.
异常出现于程序在正常情况下无法再继续运行了,此时程序就会从所给环境中跳出,抛出异常,new一个异常对象,跳转到异常处理程序
异常的体系结构
Throwable这个类是所有异常的父类,有error和exception两个子类.
error是不能被程序员处理的,很少出现.如果出现了我们应该修正代码,而不是去捕获错误,比如单词拼写错误.
exception是可以被try-catch或者throw方法抛出并处理的.
异常追踪机制
从发生点开始抛出,一直到调用程序有异常捕获机制的时候进行处理,否则就一直到main抛出给JRE,最后导致程序终止.
异常的处理方式
看两个例子,来自于:https://how2playlife.com/2019/09/10/10Java%E5%BC%82%E5%B8%B8/
首先是一个可能会抛出的程序,文件招不到,read方法抛出.
@Test
public void testException() throws IOException
{
//FileInputStream的构造函数会抛出FileNotFoundException
FileInputStream fileIn = new FileInputStream("E:\\a.txt");
int word;
//read方法会抛出IOException
while((word = fileIn.read())!=-1)
{
System.out.print((char)word);
}
//close方法会抛出IOException
fileIn.close();
}
使用try…catch…finally来解决
public class 异常处理方式 {
@Test
public void main() {
try{
//try块中放可能发生异常的代码。
InputStream inputStream = new FileInputStream("a.txt");
//如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。
int i = 1/0;
//如果发生异常,则尝试去匹配catch块。
throw new SQLException();
//使用1.8jdk同时捕获多个异常,runtimeexception也可以捕获。只是捕获后虚拟机也无法处理,所以不建议捕获。
}catch(SQLException | IOException | ArrayIndexOutOfBoundsException exception){
System.out.println(exception.getMessage());
//每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。
//catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
//在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
//如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
//如果try中没有发生异常,则所有的catch块将被忽略。
}catch(Exception exception){
System.out.println(exception.getMessage());
//...
}finally{
//finally块通常是可选的。
//无论异常是否发生,异常是否匹配被处理,finally都会执行。
//finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。
}
finally部分的执行次序
在同一try…catch…finally块中 ,如果try中抛出异常,且有匹配的catch块,则先执行catch块,再执行finally块。如果没有catch块匹配,则先执行finally,然后去外面的调用者中寻找合适的catch块。
在同一try…catch…finally块中 ,try发生异常,且匹配的catch块中处理异常时也抛出异常,那么后面的finally也会执行:首先执行finally块,然后去外围调用者中寻找合适的catch块。
只有在try里面是有System.exit(0)来退出JVM的情况下finally块中的代码才不会执行。
自定义异常
如果要自定义异常类,则扩展Exception类即可,因此这样的自定义异常都属于检查异常(checked exception)。
如果要自定义非检查异常,则扩展自RuntimeException。
自定义异常必须包含如下的构造函数:
一个无参构造函数
一个带有String参数的构造函数,并传递给父类的构造函数。
一个带有String参数和Throwable参数,并都传递给父类构造函数
一个带有Throwable 参数的构造函数,并传递给父类的构造函数。
注意事项
1,当子类重写父类的带有 throws声明的函数时,其throws声明的异常必须在父类异常的可控范围内——用于处理父类的throws方法的异常处理器,必须也适用于子类的这个带throws方法 。这是为了支持多态。
2,Java程序可以是多线程的。每一个线程都是一个独立的执行流,独立的函数调用栈。如果程序只有一个线程,那么没有被任何代码处理的异常 会导致程序终止。如果是多线程的,那么没有被任何代码处理的异常仅仅会导致异常所在的线程结束。也就是说,Java中的异常是线程独立的,线程的问题应该由线程自己来解决,而不要委托到外部,也不会直接影响到其它线程的执行。
3,在 try块中即便有return,break,continue等改变执行流的语句,finally也会执行。
为了避免这个问题,有以下建议:
不要在fianlly中使用return。
不要在finally中抛出异常。
减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的。
将尽量将所有的return写在函数的最后面,而不是try … catch … finally中。
经典面试题回答
(引用自https://how2playlife.com/2019/09/10/10Java%E5%BC%82%E5%B8%B8/)
- Java中的NullPointerException和ArrayIndexOutOfBoundException之间有什么相同之处?
这两个异常都是非检查型异常,都继承自RuntimeException。该问题可能会引出另一个问题,即Java和C的数组有什么不同之处,因为C里面的数组是没有大小限制的,绝对不会抛出ArrayIndexOutOfBoundException。
2)在Java异常处理的过程中,你遵循的那些最好的实践是什么?
-
调用方法的时候返回布尔值来代替返回null,这样可以 NullPointerException。由于空指针是java异常里最恶心的异常
-
catch块里别不写代码。空catch块是异常处理里的错误事件,因为它只是捕获了异常,却没有任何处理或者提示。通常你起码要打印出异常信息,当然你最好根据需求对异常信息进行处理。
-
能抛受控异常(checked Exception)就尽量不抛受非控异常(checked Exception)。通过去掉重复的异常处理代码,可以提高代码的可读性。
-
绝对不要让你的数据库相关异常显示到客户端。由于绝大多数数据库和SQLException异常都是受控异常,在Java中,你应该在DAO层把异常信息处理,然后返回处理过的能让用户看懂并根据异常提示信息改正操作的异常信息。
-
在Java中,一定要在数据库连接,数据库查询,流处理后,在finally块中调用close()方法。
-
既然我们可以用RuntimeException来处理错误,那么你认为为什么Java中还存在检查型异常?
这是一个有争议的问题,在回答该问题时你应当小心。虽然他们肯定愿意听到你的观点,但其实他们最感兴趣的还是有说服力的理由。其中一个理由是,存在检查型异常是一个设计上的决定,受到了诸如C++等比Java更早编程语言设计经验的影响。绝大多数检查型异常位于java.io包内,这是合乎情理的,因为在你请求了不存在的系统资源的时候,一段强壮的程序必须能够优雅的处理这种情况。通过把IOException声明为检查型异常,Java 确保了你能够优雅的对异常进行处理。另一个可能的理由是,可以使用catch或finally来确保数量受限的系统资源(比如文件描述符)在你使用后尽早得到释放 -
Java中final,finalize,finally关键字的区别
final和finally是Java的关键字,而finalize则是方法。final关键字在创建不可变的类的时候非常有用,只是声明这个类是final的。而finalize()方法则是垃圾回收器在回收一个对象前调用,但也Java规范里面没有保证这个方法一定会被调用。finally关键字是唯一一个和这篇文章讨论到的异常处理相关的关键字。在你的产品代码中,在关闭连接和资源文件的是时候都必须要用到finally块。