目录
-
异常概念
Java异常就是Java程序在运行过程中发生的一些不正常事件,例如除0、空指针、读文件不存在等。如果不对异常进行处理,则会导致程序中断执行。
-
异常分类
Throwable是Java语言中所有异常和错误的顶层父类,它有2个子类实例,分别为Error和Exception,Exception的子类实例又包括其他的异常类和RuntimeException异常类。同时,异常类可根据是否需要必须处理来分为检查异常和非检查异常,其中检查类异常为Exception及其所有子类(不包括RuntimeException及其子类)异常,检查类异常必须进行异常处理,否则编译会不通过;非检查类异常为Error和RuntimeException及其子类的异常,非检查类的异常可以不处理。异常分类如下图所示:
-
异常处理机制
Java通过2种方式来处理异常,一种是使用try-catch-finally 进行捕获处理,一种是通过throw-throws抛出异常,让调用者来处理。 try-catch-finally中,try用于监控代码块,catch用于捕获代码块中的异常并进行处理,finally用于做善后处理,无论是否发生异常,finally都会执行;throw-throws中,throw用于手动抛出异常,throws用于声明可能会抛出的异常。
-
try-catch-finally
在try-catch-finally中,catch或finally可以省略,但不能同时省略。try{}块中放的是需要监控的代码块,其可能抛出异常也可能不抛出异常;catch(){}对异常进行捕获,并对异常进行处理,其中可以设置多个catch块,分别处理不同的异常,如果try块监控到异常,则catch块处理完异常后将不再执行try块中其余的数据;finally块通常做一些清理的工作,不管会不会发生异常,finally都会执行,除了调用System.exit(0)。代码展示如下:
try {
System.out.print("aa");
int i = 10 / 10;
int[] arr = new int[1];
System.out.println(arr[1]);
//以下语句在上面语句抛出异常后将不再执行
System.out.print("bb");
} catch(ArithmeticException aex) {
System.out.print("cc");
} catch (IndexOutOfBoundsException iex) {
//虽然可捕获多个异常,但只有此异常被捕获
System.out.print("dd");
} finally {
//不管有没有捕获异常,以下语句都会执行(除了在此之前执行System.exit(0))
System.out.print("ee");
}
// 执行结果为aaddee
try {
return ;
} catch(ArithmeticException aex) {
System.out.print("333");
} finally {
//不管有没有捕获异常,以下语句都会执行(除了在此之前执行System.exit(0))
System.out.print("444");
}
// 执行结果为444
try {
System.out.print("aa");
int i = 10 / 0;
System.exit(0);
} catch(ArithmeticException aex) {
System.out.print("cc");
} finally {
//由于在try块中执行了System.exit(0),所以此处代码不会再执行
System.out.print("ee");
}
// 执行结果为aa
-
throw和throws
throw 用于方法中,可以在任何地方手动抛出一个具体类型的异常;throws 则用于声明方法可能会抛出异常,调用此方法的需要处理此异常(当然也可以不处理,直接抛向更高层的调用),如果有多个异常类,则声明时各个异常类直接用英文逗号隔开;
public static int divideOne(int numOne, int numTwo) {
if (numTwo == 0) {
// throw手动抛出异常
throw new ArithmeticException("除数不能为0");
}
return numOne / numTwo;
}
//声明方法可能会抛出IOException
public static void fun1() throws IOException {
FileInputStream fis = new FileInputStream("a.txt");
}
-
自定义异常
在开发过程中除了可以使用Java提供给我们的异常外,还可以使用自己定义的异常类来处理异常。如果要自定义检查类异常,则继承Exception类即可,如果要自定义非检查类异常,则继承RuntimeException类。按照惯例一般自定义的异常类需要包含以下几种构造方法:
1、一个无参构造函数
2、一个带有String参数的构造函数,并传递给父类的构造函数。
3、一个带有String参数和Throwable参数,并都传递给父类构造函数
4、一个带有Throwable 参数的构造函数,并传递给父类的构造函数。
下面看一个简单的自定义异常:
class MyException extends Exception {
private static final long serialVersionUID = -7900053177539260954L;
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
public MyException(String message, Throwable cause) {
super(message, cause);
}
public MyException(Throwable cause) {
super(cause);
}
}
-
异常链
在一些大型软件开发过程中,某个地方发生异常可能会导致调用它的方法也发生异常,此时就会导致一连串的异常,此时后面处理的异常在抛出异常时就会将初始发生异常的信息掩盖掉,这就会导致原始异常信息的丢失,而异常链就是处理这种情况的,它会将异常串联起来使得初始的异常信息不会丢失。异常链化是如何实现的呢?主要就是异常中的带Throwable参数的方法实现的,而这个参数(cause)我们称为根源异常。看下面一个例子:
public static void main(String[] args) throws Exception {
fun1();
}
public static void fun1() throws Exception {
try {
fun2();
} catch(NullPointerException ex) {
// fun3 抛出了异常,此处重新抛出了异常,但实际的打印结果中是没有fun3函数抛出的异常信息的
throw new Exception("fun1:空指针");
}
}
public static void fun2() {
fun3();
}
public static void fun3() {
throw new NullPointerException("fun3:空指针");
}
执行结果如下:
Exception in thread "main" java.lang.Exception: fun1:空指针
at com.base.exception.ExceptionDemo.fun1(ExceptionDemo.java:13)
at com.base.exception.ExceptionDemo.main(ExceptionDemo.java:6)
出现这种情况我们可以调用带有Throwable类型参数的方法进行处理,这样就不会让原始异常丢失了,代码如下:
public static void fun1() throws Exception {
try {
fun2();
} catch(NullPointerException ex) {
// 此处调用了带Throwable类型参数的构造方法,所以根源异常信息就被保存了下来
throw new Exception("fun1:空指针", ex);
}
}
执行结果为:
Exception in thread "main" java.lang.Exception: fun1:空指针
at com.base.exception.ExceptionDemo.fun1(ExceptionDemo.java:14)
at com.base.exception.ExceptionDemo.main(ExceptionDemo.java:6)
Caused by: java.lang.NullPointerException: fun3:空指针
at com.base.exception.ExceptionDemo.fun3(ExceptionDemo.java:23)
at com.base.exception.ExceptionDemo.fun2(ExceptionDemo.java:19)
at com.base.exception.ExceptionDemo.fun1(ExceptionDemo.java:11)
... 1 more