一、异常的概念和体系结构
在理想状态下用户输入数据的格式永远是正确的,并且永远不会出现bug,但是在现实世界里,充满了不良的数据和带有问题的代码,找不到文件,服务器出现故障。
在java中,将程序执行过程中发生的不正常行为称为异常,比如:算术异常,数组越界异常,空指针异常···
在Java中不同类型的异常,都会有与其对应的类来进行描述。
异常的种类繁多,为了对不同异常或者错误进行很好的分类管理,java内部维护了一个异常的体系结构。
Throwable是异常处理的顶层类,其会派生出两个重要的子类Error和Exception。如果Java中内置的异常类不能够满足要求,可以创建自己的异常类。
Error指的是Java虚拟机无法解决的严重问题,比如JVM的内部错误、资源耗尽等,应用程序不会抛出这种类型的对象,如果出现了这样的内部错误,除了通告给用户,并尽力使程序安全终止外,就再也无能无力了。
Exception异常产生后可以通过代码进行处理,使程序继续执行。比如算术异常等,我们常说的异常就是指的Exception异常。
异常可能绘制编译时发生,也可能在程序运行时发生,根据发生的时机不同可以将异常分为编译时异常和运行时异常。
运行时异常又称非受查异常(unchecked),其派生于Error和RuntimeException类,是在程序执行期间发生的异常。
编译时异常,是在程序编译期间发生的异常也称为受检查异常(checked),除非受查异常外的所有异常就是编译时异常。
编译时出现的语法型错误,不能称之为异常。
二、异常处理
假设在一个Java程序运行期间出现一个错误,我们期望在出现错误程序可以采用一些理智的行为,如果由于出现错误而引起1某些操作没有完成,程序应该还会一种安全的状态并且能够让用户执行一些其它命令或者允许用户保存所有操作的结果,并以妥善的方式终止程序。所以为了能够在程序中处理异常情况我们可以采用一下方法。
2.1防御式编程
2.1.1LBLY(Look Before You Leap)在操作前做好充分的检查,即事前防御性
对程序中可能出现异常的问题进行判断,并直接对其处理。这种写法的缺陷会将正常流程与错误处理流程代码混在一起,使代码整体比较混乱。
2.2.2EAFP:时候获取原谅比事前获取许可更加的容易,也就是先操作,遇到问题再处理,即事后认错型
我们可以通过try/catch语句来捕获异常。
int[] arr = null;
try{
System.out.println(arr.length);
}catch (NullPointerException e){
System.out.println("出现空指针异常");
}
System.out.println("继续执行");
try{
System.out.println(10/0);
}catch (ArithmeticException e){
System.out.println("出现算术异常");
}
System.out.println("继续执行");
如果在try语句块中的任何代码抛出了一个在catch子句中说明的异常类那么程序将跳过try语句块的其余代码,并执行catch子句中的处理器代码。如果try语句中的代码没有抛出任何异常,那么程序将跳过catch子句,如果try语句块中出现异常,但catch子句中没有捕获到对应异常,就会将该异常交给JVM去处理,程序就会异常终止。
如果想要快速定位异常出现的位置可以使用printStackTrace()。
try{
}catch (NullPointerException e){
e.printStackTrace();
}
使用EAPP的方式,将正常流程与错误流程分离开,使代码更清晰,容易理解异常处理的核心思想就是EAPP,在Java中异常处理的主要五个关键字throw,try,catch,final,throws。
2.2异常的抛出
在编写程序使,如果程序中出现错误,此时就需要将错误信息告诉调用者,在Java中可以借助throw关键字抛出一个指定的异常对象,将错误信息告知给调用者
throw new XXXException("异常产生的原因");
例如:
public static int getElement(int[] array, int index){
if(index < 0 || index >= array.length){
throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");
}
return array[index];
}
public static void main(String[] args) {
int[] array1 = {1,2,3};
getElement(array1,3);
}
注意:
- throw必须写在方法体内部
- 抛出的对象必须是Exception或者Exception的子类对象
- 如果抛出的是RunTimeException或者RunTimeException的子类则可以直接交给JVM来处理
- 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
- 一旦方法抛出异常,这个方法就不可能返回到调用者,其后代码不会执行,所以不必为返回的默认值或错误代码担忧
2.3异常的捕获
异常的捕获是异常的具体处理方式,如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息,其中包括异常的类型和堆栈的内容。要想捕获一个异常必须设置try/catch语句块
try{
// 将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e){
// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基时,就会被捕获到
// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
}
try块抛出异常位置之后的代码将不会被执行,如果抛出异常类型与catch时的异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续向外抛,直到JVM收到中断程序
try中可能会抛出多个不同的异常对象,则必须使用多个catch来捕获异常——即多种异常,多次捕获。
try{
int[] arr1 = null;
System.out.println(arr1.length);
System.out.println(10/0);
}catch (NullPointerException e){
System.out.println("空指针异常");
e.printStackTrace();
}catch (ArithmeticException e){
System.out.println("算术异常");
e.printStackTrace();
}
也可以写作:
try{
int[] arr1 = null;
System.out.println(arr1.length);
System.out.println(10/0);
}catch (NullPointerException | ArithmeticException e){
e.printStackTrace();
}
如果异常之间具有父子关系一定是子类异常在前,父类异常在后
try{
int[] arr1 = null;
System.out.println(arr1.length);
}catch (NullPointerException e){
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
在写程序时有些特定代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库连接、io流等在程序正常或者异常退出时必须对资源进行回收,因为异常会引发程序的跳转,可能会导致有些语句执行不到,此时我们可以使用finally来解决这个问题。
try{
// 可能会发生异常的代码
}catch(异常类型 e){
// 对捕获到的异常进行处理
}finally{
// 此处的语句无论是否发生异常,都会被执行到
}
在finally中经常会进行一些资源清理的扫尾工作
finall执行的时机是在方法返回之前,当try或者catch中如果有return会在这个return之前执行finally但是如果finally中也存在return语句那么就会执行finally中的语句,从而不会执行try中的return,所以一般不建议在finally中写return.
2.4异常处理流程
方法之间是存在相互调用关系的, 这种调用关系我们可以用 "调用栈" 来描述.
在 JVM 中有一块内存空间称为"虚拟机栈" 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace(); 的方式查看出现异常代码的调用栈.如果本方法中没有合适的处理异常的方式, 就会沿着调用栈向上传递,如果向上传递都没有合适的方法处理异常最终就会交给JVM来处理,程序就会异常终止
3、自定义异常类
虽然Java中已经内置了丰富的异常类,但是并不能完全表示实际开发中所遇到的一些异常,此时就需要维护符合我们实际情况的异常结构,我们需要自定义异常类,可以继承字Exception或者RunTimeExcption。继承自Exception的异常默认为受查异常,继承自RunTimeException的异常默认是非受查异常
public class NumberException extends Exception{
public NumberException(String message){
super(message);
}
}
在这个自己实现的异常类中需实现一个带有String类型参数的构造方法,这个参数含义表示出现异常的原因。
class Number {
public void number(int num) throws NumberException{
if(num > 9){
throw new NumberException("数字大于10");
}
}
}
public class Num {
public static void main(String[] args) {
try{
int num = 10;
new Number().number(num);
} catch (NumberException e) {
e.printStackTrace();
}
}
}