java.lang
类 Throwable
java.lang.Object
继承者 java.lang.Throwable
所有已实现的接口:
java.io.Serializable
直接已知子类:
Error, Exception
Throwable类是所有异常信息的超类(父类)。
直接子类:Error类,Exception类。
Error类:
是程序无法处理的错误,表示运行应用程序中较严重问题。
就是程序运行时候抛出的最严重级别的错误了,如VirtualMachineError,ThreadDeath。抛出了Error的程序从Java设计的角度来讲,程序基本不可以通过后续代码修复,从而理应终止。当然,从语法上来讲,所有这些都可以被写进catch里面,但是Error因为上述原因,不应该被代码处理。
Exception类:
有三种类型:编译时异常CheckedException, 运行时异常RuntimeException, 自定义异常。
运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
自定义异常:sun提供了很多的异常类给我们用于描述程序中各种的不正常情况,但是sun给我们提供异常类还不足以描述我们现实生活中所有不正常情况,那么这时候我们就需要自定义异常类。
一般在开发中,自定义的异常都是 运行时异常。
异常处理机制:(重点)
在 Java 应用程序中,异常处理机制为:抛出异常,捕获异常。
抛出异常:
throws:当前方法不处理,而是声明抛出,由该方法的调用者来处理;
抛出异常有两种方式:
throws:运用于方法声明之上,定义于方法参数之后,表示当前方法不处理异常,而是提醒该方法的调用者来处理抛出的异常(一个或者多个异常);如:
private static int divide(int num1, int num2) throws Exception {}
throw:运用于方法内部,抛出一个具体的异常对象,中止方法的执行,其语法格式如下:
throw new 异常类("异常信息");
补充:如果每一个方法都放弃处理异常都直接通过
throws
声明抛出,最后异常会抛到main
方法,如果此时main
方法还不处理,会继续抛出给JVM
,JVM
底层的处理机制就是打印异常的跟踪栈信息(e.printStackTrace()方法);runtime
异常,默认就是这种处理方式。
捕获异常:
try-catch:在当前方法中使用try-catch的语句块来处理异常;
try{
System.out.println("begin");
System.out.println("10/0="+(10/0));
System.out.println("try end");
}catch (Exception e) {
System.out.println(e.getMessage());//获取异常信息
System.out.println(e); //调用toString方法,打印异常类名和异常信息
e.printStackTrace(); //jvm默认就用这种方式处理异常,异常栈追踪
}
运行结果如下:
begin
/ by zero
java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
at com.yjxxt.exception.Test3.main(Test3.java:7)
通过查看运行结果,不难发现,使用try-catch
之后,程序遇到异常时不再中断执行,而是跳过异常代码及其之后的try代码块
中的剩余代码语句,来到catch
代码块中执行我们定义的异常处理代码;
在上述案例中是打印出了异常信息,异常对象信息,异常栈追踪;其中的3
个方法都是Throwable类的方法:
-
String getMessage()
:获取异常的详细描述信息; -
String toString()
:获取异常的类型、异常描述信息; -
void printStackTrace()
:打印异常的跟踪栈信息并输出到控制台,但不能在System.out.println()
中使用该方法;其中包含了异常的类型、异常的原因、异常出现的位置;在开发和调试阶段,该方法都很有用,方便调试和修改;
捕获多个异常:
try{
System.out.println("begin");
int num=Integer.valueOf("abc");//异常1
System.out.println(num);
System.out.println("10/0="+(10/0));//异常2
System.out.println("try end");
}catch (ClassCastException e) {
System.out.println(e.getMessage());//获取异常信息
System.out.println(e); //调用toString方法,打印异常类名和异常信息
e.printStackTrace();
} catch (Exception e) {
System.out.println(e.getMessage());//获取异常信息
System.out.println(e); //调用toString方法,打印异常类名和异常信息
e.printStackTrace(); //jvm默认就用这种方式处理异常,异常栈追踪
}finally {
System.out.println("end");
}
运行结果如下:
begin
For input string: "abc"
java.lang.NumberFormatException: For input string: "abc"
end
java.lang.NumberFormatException: For input string: "abc"注意:
一个try后面可以接1~n个catch
catch会从上到下判断执行
try中的代码如果不出现异常,不会执行catch的判断
如果try中一旦出现了异常,直接执行对应catch的判断,try中后面的代码不会执行(即使 后面语句有异常,也不执行)
无论try中是否会出现异常,无论出现了异常执行了哪一个catch,最后都会执行finally中的 代码
finally中一般定义为资源的关闭等
finally代码块
finally
语句块表示无论如何(也包括发生异常时)都会最终执行的代码块,比如:当我们在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),在使用完之后,都得最终关闭打开的资源。
接下来讲一个重要的知识点
JAVA中try、catch、finally带return的执行顺序总结
异常处理中,try、catch、finally的执行顺序,大家都知道是按顺序执行的。即,如果try中没有异常,则顺序为try→finally,如果try中有异常,则顺序为try→catch→finally。但是当try、catch、finally中加入return之后,就会有几种不同的情况出现,下面分别来说明一下。
一、try中带有return
private int testReturn1() {
int i = 1;
try {
i++;
System.out.println("try:" + i);
return i;
} catch (Exception e) {
i++;
System.out.println("catch:" + i);
} finally {
i++;
System.out.println("finally:" + i);
}
return i;
}
执行结果是:
try:2
finally:3
2
因为当try中带有return时,会先执行return前的代码,然后暂时保存return的信息,再执行finally中的代码,最后再通过return返回之前保存的信息。所以,这里方法返回的值是try中计算后的2,而非finally中计算后的3。但有一点需要注意,再看另外一个例子:
private List<Integer> testReturn2() {
List<Integer> list = new ArrayList<>();
try {
list.add(1);
System.out.println("try:" + list);
return list;
} catch (Exception e) {
list.add(2);
System.out.println("catch:" + list);
} finally {
list.add(3);
System.out.println("finally:" + list);
}
return list;
}
结果是:
try:[1]
finally:[1, 3]
[1, 3]
看完这个例子,可能会发现问题,刚提到return是会临时保存需要返回的信息,不受finally中的影响,为什么这里会有变化?其实问题出在参数类型上,上一个例子用的是基本类型,这里用的引用类型。list里存的不是变量本身,而是变量的地址,所以当finally通过地址改变了变量,还是会影响方法返回值的。
二、catch中带有return
private int testReturn3() {
int i = 1;
try {
i++;
System.out.println("try:" + i);
int x = i / 0 ;
} catch (Exception e) {
i++;
System.out.println("catch:" + i);
return i;
} finally {
i++;
System.out.println("finally:" + i);
}
return i;
}
输出:
try:2
catch:3
finally:4
3
catch中return与try中一样,会先执行return前的代码,然后暂时保存需要return的信息,再执行finally中的代码,最后再通过return返回之前保存的信息。所以,这里方法返回的值是try、catch中累积计算后的3,而非finally中计算后的4。
三、finally中带有return
private int testReturn4() {
int i = 1;
try {
i++;
System.out.println("try:" + i);
return i;
} catch (Exception e) {
i++;
System.out.println("catch:" + i);
return i;
} finally {
i++;
System.out.println("finally:" + i);
return i;
}
}
输出:
try:2
finally:3
3
当finally中有return的时候,try中的return会失效,在执行完finally的return之后,就不会再执行try中的return。这种写法,编译是可以编译通过的,但是编译器会给予警告,所以不推荐在finally中写return,这会破坏程序的完整性,而且一旦finally里出现异常,会导致catch中的异常被覆盖。
总结:
1、finally中的代码总会被执行。
2、当try、catch中有return时,也会执行finally。return的时候,要注意返回值的类型,是否受到finally中代码的影响。
3、finally中有return时,会直接在finally中退出,导致try、catch中的return失效。