异常

异常分为错误(Error)与异常(Exception)

一、异常体系结构

java.lang.Throwable

  • java.lang.Error:一般不编写针对性的代码进行处理
  • java.lang.Exception:可以进行异常的处理
  • 编译时异常(checked)(也称“受检异常”)
  • IOException
  • ClassNotFoundException
  • 运行时异常(unchecked)(也称“非受检异常”)
  • NullPointerException
  • ArrayIndexOutOfBoundsException
  • ClassCastException
  • NumberFormatException
  • InputMismatchException
  • ArithmeticException

二、Java异常处理的方式

  • 方式一:try-catch-finally
    举个例子:小孩在放羊,这时狼来了,小孩发现这个狼只是个狼崽子,他自己就有能力解决。羊就该吃草还吃草,相当于代码正常执行
  • 方式二:throws + 异常类型
    这个就相当于来了一只老狼,小孩处理不了了,需要在山下喊人,让成人来解决,大人如果不能解决就会找警察。这种处理方式就相当于方式二

三、异常的处理:抓抛模型

  • 过程一:“抛”:程序在正常执行的过程中,一旦出现了异常,就会在异常代码处生成一个对应异常类的对象。并将此对象抛出。一旦抛出对象后,其后的代码就不再执行。
    关于异常对象的产生:① 系统自动生成的异常对象(在产生异常的代码出生成一个异常对象)
    ② 手动的生成一个异常对象,并抛出(throw)
public class StudentTest {
    public static void main(String[] args) {
        Student s = new Student();
        s.regist(-1001);
    }
}

class Student {
    private int id;
    
    public void regist(int id) {
        if(id > 0) {
            this.id = id;
        } else {
            //System.out.println("您输入的数据非法!");//这里如果输入的数据非法,他还会返回一个id的默认值,还是会出来一个结果。按理说输入非法应该报错
            //手动抛出一个异常对象,一般这个对象都是运行时异常,或编译时异常。如果是编译时异常就需要捕获处理
            //手动抛出运行时异常,编译可以通过,可以不用处理
            //throw new RunTimeException("您输入的数据非法!");
            
            //手动抛出编译时异常,编译都不会通过,这时就需要处理了
            try {
            	throw new Exception("您输入的数据非法!");//手动抛出异常
            } catch(Exception e) {
                System.out.println(e.getMessage());
            }
            
        }
    }
    @Override
    public String toString() {
        return "Student " + "[id = " + id + "]";
    }
}
  • 过程二:“抓”:可以理解为异常的处理方式:① try-catch-finally ② throws

四、异常处理方式一:try-catch-finally 的使用

try {
    //可能出现异常的代码
}catch(异常类型1 变量名1){
    //处理异常的方式1
}catch(异常类型2 变量名2){
    //处理异常的方式2
}catch(异常类型3 变量名3){
    //处理异常的方式3
}
...
finally{
    //一定会执行的代码
}

说明:

  • finally是可选的。
  • 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
  • 一旦try中的异常对象匹配到某个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况)。继续执行其后的代码。
  • catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
    catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错
  • 常用的异常对象的处理的方式:① getMessage()(返回值类型是String) ② printStackTrace()(void,无返回值,其抛出的异常信息包含getMessage()抛出的,所以比较常用)
  • 在try结构中声明的变量,在出了try结构以后,就不能再被调用
  • try-catch-finally结构可以嵌套。

体会1:使用try-catch-finally处理编译时异常,使得程序在编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。

体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。

针对编译时异常,我们说一定要考虑异常的处理。

五、finally

  • finally中声明的是一定会被执行的代码。即使catch中 出现异常了,try中有return语句,catch中有return语句等情况。
public void testMethod() {
    int num = method();
    System.out.println(num);
}

public int method() {
    try{
        int[] arr = new int[10];
        System.out.println(arr[10]);//①
        return 1;
    }catch(ArrayIndexOutOfBoundsException e) {
        e.printStackTrace();
        return 2;
    } finally {
        System.out.println("我一定会被执行!");
        //return 3;
    }
}

如果不注释①号代码,则输出语句为:我一定被执行!/n 2

如果注释掉:输出:我一定被执行!/n 1

如果给finally里面加一个语句return 3; 则不管有没有异常都输出:输出:我一定被执行!/n 3

来个情景对话方便理解:如果程序存在异常,那么程序执行到catch代码块中。先执行e.printStackTrace();然后准备返回2,finally说等等,我还没执行,我先执行。catch的return说,好,那你先执行,执行完了给我说一声。然后finally就先执行第一句代码,然后直接返回3,并没有catch中return返回的机会。

不管是遇到了一个return还是制造的异常,都先执行finally的语句

  • 像数据库链接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要手动的进行资源的释放。此时的资源释放,就需要声明在finally中。

六、异常处理方式二:throws + 异常类型

  • "throws + 异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
    一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后的异常类型时,就会被抛出。异常代码后续的代码,就不再执行!
  • 体会:try-catch-finally:真正的将异常处理掉了
    throws的方式只是将异常抛给了方法的调用者。并没有真正将异常处理掉。(最多抛到main方法里,你就需要用try-catch-finally将他处理了)
    归根结底,最后都是需要用try-catch(-finally)来解决
  • 在开发过程中如何选择使用try-catch-finally还是throws?
  • 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
  • 执行的方法a中,先后又调用了另外的几个方法,这几个方法时递进关系执行的(比如:方法三要用到方法二得到的结果,方法二又需要方法一得到的结果)。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方法进行处理。

七、如何自定义异常类?

  • 继承于先有的异常结构:RunTimeException、Excetion
  • 提供全局常量:serialVersionUID
  • 提供重载的构造器
public class MyException extends RunTimeException {
    static final long serialVersionUID = -4321532432453543543L;
    
    public MyException() {
        
    }
    
    public MyException(String msg) {
        super(msg);
    }
}

八、throw和throws的区别:

throw表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内

throws属于异常处理的一种方式,声明在方法的声明处。