目录
异常处理概念介绍
error
exception
异常的分类
异常的控制操作
异常的捕获(try/catch)
异常的抛出(throw)
异常链
捕获程序的流程
Java异常都是对象,是Throwable子类的实例,当程序出现异常时,需要程序进行相应控制
throwable有两个子类分别是error以及exception
error指的是错误,大多数时候并非人为产生,而是由于JVM虚拟机内存溢出等情况产生的。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。
exception指的是异常,即由于程序的逻辑缺陷导致的可以被处理控制的错误。
异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。
我们面对的主要是exception及其子类。
exception的子类主要又分为两种:runtimeException以及非runtimeException,runtimeException类及其子类表示“JVM 常用操作”引发的错误,比如喜闻乐见的空指针以及下标越界等等,runtimeException及其子类也都是unchecked exceptions。
1·
Java的异常(包括Exception和Error)分为可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。
可查的异常指某些程序处理的过程中,系统默认为该程序会引发异常或极大概率引发异常,除了runtimeException及其子类,其他的Exception的子类都是可查异常。比如上图的IOException,这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
不可查异常就是runtimeException及其子类以及错误,程序默认我们撰写的代码逻辑正确,因此当关键参数为0时,程序没有数据参考无法判断是否会出问题,因此这类异常需要我们在逻辑上认真对待或者主动去控制异常的发生。
2·
Exception(可处理异常)又分为运行时异常以及非运行时异常
运行时异常都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
注:1.2只是概念上的区分 互相之间有交集,如IOException既是可查异常也是非运行时异常
首先无论如何需要做异常处理的地方一定需要异常捕获,try是对控制异常的代码块,try中出现异常后,会在catch下寻找对应的异常类的处理方法,然后进项输出异常日志,异常来源,以及自定义的异常处理等。一般在try/catch后还会追加finally代码块,用来在异常后做一些必须的程序处理,比如关闭io流,异常数据复位等操作。
例子:当书写一段会空指针的代码时,如果提前做好捕获,则异常情况出现后由try提出异常,之后在catch中找到异常对应的处理方式,如 e.printStackTrace();,输出异常日志,这是由程序员手动控制的异常流程。
而我们已经知道空指针异常是不可查异常之一,也就是说系统会默认编译通过,如果出现异常则是由系统自身抛出异常日志,虽然最后结果相同,但是实现思路有区别,这里要区分。
throw总是出现在函数体中,用来抛出一个Throwable类型的异常。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。
该语句的语法格式为:
throw new exceptionname;
例如抛出一个IOException类的异常对象:
hrow new IOException;
要注意的是,throw 抛出的只能够是可抛出类Throwable 或者其子类的实例对象。
如果抛出了检查异常,则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。
如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。如果抛出的是 Error或RuntimeException,则该方法的调用者可选择处理该异常。
throw的特点是可以不在本类里进行异常的处理,而是向上抛出给上一级方法进行处理,也就是他的调用方。
我个人认为这样做的目的有两点:
一个是解耦,不需要所有异常块重复的去处理相同的异常情况,由一个调用者统一处理即可。
一个是自定义异常类,即集成Exception即可,我们知道trycatch只能捕获系统定义好的异常,并不支持我们业务上需要抛出的异常需求,这样就可以自定义一个异常类,之后业务上遇到相关逻辑的时候,抛出他即可。
举个例子:
package Test;
import java.lang.Exception;
public class TestException {
static int quotient(int x, int y) throws MyException { // 定义方法抛出异常
if (y < 0) { // 判断参数是否小于0
throw new MyException("除数不能是负数"); // 异常信息
}
return x/y; // 返回值
}
public static void main(String args[]) { // 主方法
int a =3;
int b =0;
try { // try语句包含可能发生异常的语句
int result = quotient(a, b); // 调用方法quotient()
} catch (MyException e) { // 处理自定义异常
System.out.println(e.getMessage()); // 输出异常信息
} catch (ArithmeticException e) { // 处理ArithmeticException异常
System.out.println("除数不能为0"); // 输出提示信息
} catch (Exception e) { // 处理其他异常
System.out.println("程序发生了其他的异常"); // 输出提示信息
}
}
}
class MyException extends Exception { // 创建自定义异常类
String message; // 定义String类型变量
public MyException(String ErrorMessagr) { // 父类方法
message = ErrorMessagr;
}
public String getMessage() { // 覆盖getMessage()方法
return message;
}
}
上图可知,异常触发了一个处理catch之后,就结束了不会触发其他的。
比如上面参数如果b是负数,则触发MyException,如果b=0,则触发ArithmeticException,其他情况才会触发Exception。
try/catch下
如果出现异常则流程为try-catch-finally
未出现异常则是try-finally
但是有一种特殊情况是,如果try的代码块里有return,那么finally是否还会执行,答案是依然会执行,但是finally会作为一个void方法来执行,不会对return相关值产生影响,比如int x=1;try里return ++x;finally里++x,那么debug步骤为:先到return这一行 x=2,然后到finally x=3,然后回答哦哦return这一行 打印结果x依然=2;说明return时的值被置成一个临时变量,finally作为一个执行过程结束后,return这个临时变量回来即可。
public static int xx(){
int x=1;
try {
return ++x;//2 debug先走到这里,然后到finally,然后再返回这里,x还是第一次走到这里的那个值
} finally {
++x; //3
}
}
如果try与finally中都有return函数,那么try中的return将被略过(不只是return动作被略过,++x的操作也不会执行,这一点与上述不同),直接以finally的return结束。
public static int xx(){
int x=1;
try {
return ++x; //这里直接被略过
} finally {
return ++x;
}
}