一、异常分类
1.Java 标准库内建了一些通用的异常,这些类以 Throwable 为顶层父类。
Throwable 又派生出 Error 类和 Exception 类。
-
错误:Error 类以及他的子类的实例,代表了 JVM 本身的错误。错误不能被程序员通过代码处理,Error 很少出现。因此,程序员应该关注 Exception 为父类的分支下的各种异常类。
-
异常:Exception 以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被 Java 异常处理机制使用,是异常处理的核心。
异常与错误的区别是:异常可以通过程序自身捕捉处理,而错误是程序自身无法处理的。
2.Java 异常包括 Exception 类和 Error 类,其可分为可查异常和不可查异常:
-
可查异常:编译器要求必须处理的异常,这类异常的发生在一定程度上是可以预计的,而且这类异常一旦发生,就必须采用某种方式进行处理。除了RuntimeException及其子类以外的其它异常类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,出现这种异常,要么用try-catch语句捕捉它,要么用throws语句声明抛出它,否则编译不通过。
-
不可查异常:编译器不要求强制处理的异常,包括运行时异常(RuntimeException与其子类)和错误(Error) 。
3.Exception 异常又可分为两大类:运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。
- 运行时异常:RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,**这些异常是不可查异常,程序中可以选择捕获处理,也可以不处理。**这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
运行时异常的特点:Java编译器不会检查它,也就是说,当程序中出现这类异常时,也会编译通过。
- 非运行时异常(编译异常) :是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
注:编译异常就是可查异常,也就是RuntimeException以外的异常;不可查异常是运行时异常和错误的集合。
二、异常处理机制
1.在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。
-
抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
-
捕捉异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
2.对于运行时异常、错误或可查异常,Java技术所要求的异常处理方式有所不同。
-
由于运行时异常的不可查性,为了更合理、更容易地实现应用程序,Java规定,运行时异常将由Java运行时系统自动抛出,允许应用程序忽略运行时异常。
-
对于方法运行中可能出现的Error,当运行方法不欲捕捉时,Java允许该方法不做任何抛出声明。因为,大多数Error异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常。
-
对于所有的可查异常,Java规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉可查异常时,它必须声明将抛出异常。
3.能够捕捉异常的方法,需要提供相符类型的异常处理器。所捕捉的异常,可能是由于自身语句所引发并抛出的异常,也可能是由某个调用的方法或者Java运行时 系统等抛出的异常。也就是说,一个方法所能捕捉的异常,一定是Java代码在某处所抛出的异常。简单地说,异常总是先被抛出,后被捕捉的。
总体来说,Java规定:对于可查异常必须捕捉、或者声明抛出。允许忽略不可查的RuntimeException和Error。
三、异常处理语法
(一)异常处理
1.运行时异常
出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果没有处理块,到最上层,如果是多线程就由 Thread.run () 抛出,如果是单线程就被 main () 抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,整个程序也就退出了。运行时异常是 Exception 的子类,也有一般异常的特点,是可以被 Catch 块处理的。只不过往往不对它处理罢了。也就是说,如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。
2.编译异常
在编写代码处理异常时,对于检查异常,有 2 种不同的处理方式:
- 使用 try…catch…finally 语句块处理它。
- 在函数签名中使用 throws 声明交给函数调用者 caller 去解决。
实例:
-
A方法存在异常风险
-
main方法调用A方法
-
情况1:
A使用try-catch处理了异常,则main方法不需要做额外处理 -
情况2:
A在方法签名中throws了异常,此时main需要做出处理,有两种方式,第一就是使用try catch 捕获异常,另一种是在main方法签名中throws异常,jvm调用main方法,这是最后一层处理异常的机制了。
(二)具体语法
1.try catch finally处理
-
try 块中的局部变量和 catch 块中的局部变量(包括异常变量),以及 finally 中的局部变量,他们之间不可共享使用。
-
每一个 catch 块用于处理一个异常。异常匹配是按照 catch 块的顺序从上往下寻找的,只有第一个匹配的 catch 会得到执行。匹配时,不仅运行精确匹配,也支持父类匹配,因此,如果同一个 try 块下的多个 catch 异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个 catch 块都有存在的意义。
-
java 中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去。而try块中的异常后面的代码就不会在执行。有的编程语言当异常被处理后,控制流会恢复到异常抛出点接着执行,这种策略叫做:resumption model of exception handling(恢复式异常处理模式 )。 Java 则是让执行流恢复到处理了异常的 catch 块后接着执行,这种策略叫做:termination model of exception handling(终结式异常处理模式)
注:运行时异常不加处理语句的时候,异常出现之后程序就结束了,但是使用try catch捕获异常之后,程序并没有结束,而是在catch语句之后继续执行。
2.throws和throw关键字
throws 声明:
- 如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则 javac 保证你必须在方法的签名上使用 throws 关键字声明这些可能抛出的异常,否则编译不通过。
- throws 是另一种处理异常的方式,它不同于 try…catch…finally,throws 仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。
throw 异常抛出语句
-
程序员也可以通过 throw 语句手动显式的抛出一个异常。throw 语句的后面必须是一个异常对象。
-
throw 语句必须写在函数中,执行 throw 语句的地方就是一个异常抛出点,它和由 JRE 自动形成的异常抛出点没有任何差别。
-
在异常处理中,try 语句要捕获的是一个异常对象,那么此异常对象也可以自己抛出。
【代码示例】
public static void main(String[] args) {
try {
abc();
} catch (Exception e) {
System.out.println("int the catch first");
e.printStackTrace();
System.out.println("in the catch last");
}
System.out.println("the last");
}
public static void abc() throws Exception {
System.out.println("1");
if (true) {
throw new Exception("异常");
}
System.out.println("2");
}
【执行结果】
1
int the catch first
java.lang.Exception: 异常
at test.exceptionTest.Test.abc(Test.java:21)
at test.exceptionTest.Test.main(Test.java:7)
in the catch last
the last
四、异常注意事项
1.throws方法的重写
-
当子类重写父类的带有 throws 声明的函数时,其 throws 声明的异常必须在父类异常的可控范围内 —— 用于处理父类的 throws 方法的异常处理器,必须也适用于子类的这个带 throws 方法 。这是为了支持多态。
-
例如,父类方法 throws 的是 2 个异常,子类就不能 throws 3 个及以上的异常。父类 throws IOException,子类就必须 throws IOException 或者 IOException 的子类。
2.try catch finally中的return
-
在 try 块中即便有 return,break,continue 等改变执行流的语句,finally 也会执行。
-
也就是说:try…catch…finally 中的 return 只要能执行,就都执行了,他们共同向同一个内存地址(假设地址是 0×80)写入返回值,后执行的将覆盖先执行的数据,而真正被调用者取的返回值就是最后一次写入的。
-
finally 中的 return 会覆盖 try 或者 catch 中的返回值。
-
finally 中的异常会覆盖(消灭)前面 try 或者 catch 中的异常
【代码示例1】
public static String m1(){
try{
System.out.println("this is try method");
return "this is try return";
}catch(Exception e){
e.printStackTrace();
}finally{
System.out.println("this is finally method");
return "this is finally return";
}
}
public static void main(String[] args) {
System.out.println(m1());
}
【运行结果1】
this is try method
this is finally method
this is finally return
【代码示例2】
public static String m1(){
try{
System.out.println("this is try method");
return "this is try return";
}catch(Exception e){
e.printStackTrace();
}finally{
System.out.println("this is finally method");
// return "this is finally return";
}
return "this is last return";
}
public static void main(String[] args) {
System.out.println(m1());
}
// 三个return 同时出现会编译错误
【运行结果2】
this is try method
this is finally method
this is try return
总结:
- finally中的return和方法的return不能共存,因为finally是方法的最后执行的部分,finally执行完成,方法然后就直接结束,无论try catch finally语句块外面存在何种语句。
- 当方法和try语句块都有return的时候,不会执行方法的return,因为finally是最后执行部分,先执行try语句,然后执行finally语句,最后执行try的return语句。
【Java 面试那点事】
这里致力于分享 Java 面试路上的各种知识,无论是技术还是经验,你需要的这里都有!
这里可以让你【快速了解 Java 相关知识】,并且【短时间在面试方面有跨越式提升】
面试路上,你不孤单!
来源:CSDN
作者:温柔的谢世杰
链接:https://blog.csdn.net/qq_33945246/article/details/104746904