异常详解
🦔异常
发现错误的理想时机是在编译阶段,也就是在运行程序之前。然而编译期间并不能找到所有的错误,余下的问题必须在运行期间解决。这就需要错误源能够通过某种方式,把适当的信息传递给某个接受者——该接受者知道如何正确处理这个问题。
Java编程思想(第四版)
概念
异常指不期而至的各种状况,如除0操作、用户输入不合法、内存溢出、读取文件不存在等等情况。
Java异常是一个描述在代码段中发生异常的对象,当发生异常时,一个代表该异常的对象被创建并且在导致该异常的方法中被抛出,而该方法可以选择自己处理或者传递该异常。
使用异常不仅降低了处理错误代码的复杂度,并且使代码变得更加健壮。
异常的体系结构
Java把异常当作对象来处理,并定义一个基类java.lang.Throwable
作为所有异常的超类。
Java异常层次结构图:
在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error
和异常Exception
。
-
Error
Java虚拟机无法解决的严重问题,如:JVM内部错误,资源耗尽等情况。一般不编写针对性的代码进行处理。
-
Exception
因为其他编程错误或偶然的外在因素导致的一般性问题,如:控制针访问、视图读取不存在的文件、网络连接中断、数组索引越界等情况。可以编写针对性的代码进行处理。
遇到这些异常情况有两种解决办法:一种时抛出异常,一种是捕获异常。
如果我们遇见异常,解决是最好的,不能解决交由上层环境,如果上层环境还是不能解决,继续向上抛,直到main()方法将异常抛给JVM,JVM将使用默认的异常处理程序:终止程序,打印异常信息
捕获异常的最佳时机是在编译期间,但有些异常是在运行期间才会发生的,如:除0操作、数组索引越界
所以,Exception又分为
-
编译时异常(受检异常)
是指编译器要求必须处理的异常,是程序在运行期间由于外界因素导致的一般性错误。如果不进行处理,可能导致意想不到的结果。一般编译器会进行提示。如:IOException、SQLException、ClassException
-
运行时异常
编译不要求强制处理的异常,一般是在编程过程中的逻辑错误,可以不做处理。因为这类异常非常普遍,如进行全局处理,会影响程序的可读性以及运行效率。程序员应该积极避免这类异常。对于这类异常,或者叫Bug进行try...catch处理是毫无意义的。java规定,运行时异常将由运行时系统自动抛出,允许应用程序忽略运行时异常
除了RuntimeException和Error其余都是受检异常,会在编译期间检测异常
常见异常
-
ArrayIndexOutOfBoundsException
int[] array = {1,2,3,4,5}; for (int i = 0; i < 6; i++) { System.out.println(array[i] + ""); }
-
NUllPointException
int[] array = {1,2,3}; array = null; for (int i = 0; i < array.length; i++) { System.out.println(array[1]); }
-
ArithmeticException
int x = 0; int y = 3 / x; System.out.println(y);
-
ClassCastException
Object date = new Date(); CommonException exception = new CommonException(); exception = (CommonException) date;
异常处理机制
1)抛出异常
对于在当前环境中没办法获得更多信息去处理这个异常,或者在当前环境中处理异常没有意义的时候,我们需要把这个异常抛出这个环境,交由上层方法调用者去处理。
抛出异常后,会有几件事情发生:
- 使用new在堆上创建一个异常对象。
- 终止当前的执行路径,并且在当前环境中弹出对异常对象的引用,只要异常被捕获,根据这个异常对象的引用,就能控制程序的指向。
- 异常处理机制接管程序,并寻找一个恰当的地方(异常处理程序或异常处理器),继续执行程序,它的任务是将程序从错误状态中恢复,使程序要么换种方法运行,要么继续运行下去。
2)捕获异常
在方法抛出异常后,系统转而寻找合适的异常处理器。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当方法抛出的异常类型和异常处理器所能处理的异常类型相符,或是异常处理器处理异常类型的子类时,达到匹配。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直到找到含有合适异常处理器的方法执行,当运行时系统遍历调用栈而未找到合适的异常处理器的时候,则运行时系统终止,即Java程序终止
异常处理语法
1)try...catch
try代码块中的区域是监控区域,当try代码块中的代码出现异常的时候,自动抛出异常,创建异常对象
catch代码块用于捕获异常,执行异常程序,catch中的代码将保证程序正常运行下去
一个语句中产生多种异常时,这时需要多重catch语句,排列规则是:先小后大,先子类再父类,否则捕获底层异常的catch子句会被屏蔽
try语句可以被嵌套,如果内层try中产生异常,会先匹配内层catch语句,不匹配再和外层catch语句进行匹配
对于一些资源来说,并不属于Java资源你,不由JVM进行管理,所以我们在打开后需要自己手动关闭,可以使用try开启资源自动管理功能,执行完语句, 资源自动关闭,交由JVM进行管理
// 语法
try(打开资源){
}
public void indexExp() {
/*
* Description: 对IndexOutOfBoundsException捕获,执行PlanB
* @author: 苏无及
* @date: 2022/8/6 19:56
* @param:[]
* @return:void
*/
int[] array = {1, 2, 3, 4, 5};
try {
for (int i = 0; i < 6; i++) {
System.out.println(array[i] + "");
}
} catch (IndexOutOfBoundsException e) {
System.out.println("IndexOutOfBoundsException");
}
}
/*
测试
*/
@Test
public void test1(){
CatchException exception = new CatchException();
exception.indexExp();
}
2)throw
用于抛出异常,手动创建异常对象,不需要JVM创建对象
程序执行完throw之后立即停止执行,throw之后的语句不会执行,最邻近的try块检测自己是否有与异常对象相匹配的catchh语句,如果发现匹配的块,控制转向该语句,没有发现则使次包围的try进行检查,如果一直没有发现,程序将执行结束
所有的java内置的运行时异常创建对象,有两个构造函数,一个空参,一个带有一个字符串参数,参数用于指定异常的描述,可以通过print直接打印对象,或者使用getMessage()方法从而得到字符串
public void throwIndexExp() {
/*
* Description: throw抛出异常
* @author: 苏无及
* @date: 2022/8/7 20:43
* @param:[]
* @return:void
*/
int[] array = {1, 2, 3, 4, 5};
for (int i = 0; i < 6; i++) {
if (i == 5){
throw new IndexOutOfBoundsException("数组索引越界!");
}
System.out.println(array[i] + "");
}
}
/*
测试
*/
@Test
public void test2(){
CatchException exception = new CatchException();
try {
exception.throwIndexExp();
} catch (IndexOutOfBoundsException e) {
System.out.println("数组索引越界");
}
}
3)throws
用于在方法签名中,声明该方法可能抛出的异常类型,告知方法调用者
public void throwsIndexExp() throws IndexOutOfBoundsException{
/*
* Description: 使用throws声明异常类型
* @author: 苏无及
* @date: 2022/8/7 20:53
* @param:[]
* @return:void
*/
int[] array = {1, 2, 3, 4, 5};
for (int i = 0; i < 6; i++) {
System.out.println(array[i] + "");
}
}
/*
测试
*/
@Test
public void test3(){
CatchException exception = new CatchException();
try {
exception.throwsIndexExp();
} catch (IndexOutOfBoundsException e) {
System.out.println("数组索引越界!");
}
}
4)finally
finally语句块总会被执行,不管catch中的语句是否执行。主要用于回收在try里打开的资源。
public void finallyExp(){
/*
* Description: 使用finally
* @author: 苏无及
* @date: 2022/8/7 20:59
* @param:[]
* @return:void
*/
int[] array = {1, 2, 3, 4, 5};
try {
for (int i = 0; i < 6; i++) {
System.out.println(array[i] + "");
}
} catch (IndexOutOfBoundsException e) {
System.out.println("数组索引越界!");
} finally {
System.out.println("finally");
}
}
/*
测试
*/
@Test
public void test4(){
CatchException exception = new CatchException();
exception.finallyExp();
}
执行顺序
对于try、catch、finally的执行顺序:
- 如果try中没有异常,try-->finally
- 如果try中有异常,try-->catch-->finally
- 当try、catch中有return时,也会执行finally,注意return的时候注意是否受finally的影响
- finally中有return的时候,会直接在finally中退出,导致try、catch中的return失效
throw和throws的区别
- trow出现在函数体中,throws出现在方法签名中
- throws表示可能出现的异常类型,throw则一定抛出某个异常对象
- 两者都是抛出异常,交由上次环境进行处理,如果还是不处理,JVM默认处理
- throws可以表示抛出多个异常类型,用逗号分隔
自定义异常
- 创建异常类(见名知意)
- 继承内置异常类
- 提供有参无参构造
// 自定义年龄越界异常类
public class AgeOutOfBoundsException extends RuntimeException {
public AgeOutOfBoundsException() {
}
public AgeOutOfBoundsException(String message) {
super(message);
}
}
Throwable的成员方法
-
String getMessage()
返回此throwable的详细消息字符串
返回的是在创建异常对象时,当作参数传入的字符串
throw new IndexOutOfBoundsException("数组索引越界!"); ... java.lang.IndexOutOfBoundsException: 数组索引越界!
-
String toString()
返回此可抛出的简短描述
java.lang.ArrayIndexOutOfBoundsException: 5
-
void printStackTrace
把异常的错误信息输出在控制台
java.lang.ArrayIndexOutOfBoundsException: 5 at com.dong.exceptionDemo.CommonException.indexOutExp(CommonException.java:25) at com.dong.exceptionDemo.CommonExceptionTest.test1(CommonExceptionTest.java:20)