Day27:异常详解

异常

1.1 异常概述

异常(Exception)指程序运行中出现的不正常情况:文件找不到、网络异常、非法参数等等。

我们通过代码来了解一下:

public class Demo{
    public static void main(String[] args){
        int[] a={1,2,3};
        method(a);
    }
    public static void method(int[] a){
        Sytem.out.println(a[3]);
    }
}
//输出结果
ArrayIndexOutOfBoundsException//数组下标越界

上面我们得到异常中一个非常常见的异常:数组下标越界异常。

1.1.1 异常体系

Exception也是一个类,throwable是所有异常的超类;throwable还有一个子类是错误Error

Error:表示很严重的问题,无需处理。

Exception:称为异常类,表示程序本身可以处理的问题。

  • RuntimeException:在编译期是不检查的,出现问题后需要我们会来修改代码;(也就是说我们在写代码的过程中程序是不会报错的)
  • 非RuntimeException:在编译期就必须处理的问题,如果不处理,将不能通过编译,程序更加不会运行;(也就是写代码爆红的时候)

1.2 JVM的默认处理方案

当我们的程序出现异常的时候,如果我们不去处理,Java中的虚拟机会执行默认处理方案。

public class demo{
    public static void main(String[] args){
        int[] a={1,2,3};
        System.out.println("开始");
        method(a);
        System.out.println("结束");
    }
    public static void method(int[] a){
        System.out.println(a[3]);
    }
}
//输出结果
开始
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3//异常出现的类型
	at com.liuwei.oop.exception.demo.method(demo.java:11) //异常出现的位置
	at com.liuwei.oop.exception.demo.main(demo.java:7)
    //结束这个输出语句并没有执行

如果我们不处理异常,交给系统去处理会得到异常的原因、异常的位置输出在控制台,且终止程序运行

1.3 异常处理

我们为什么需要处理异常呢?

因为交给Java默认处理的话,会终止我们的程序运行!如果我们自己将异常处理了,可以让程序保持运行。

处理异常的方案:

  • try—catch—
  • throw

1.3.1 try—catch—

格式:

try{
    可能出现异常的代码;
}catch(异常的类型   变量名){
    如与catch中的异常类型匹配成功后需要执行的异常处理代码;
    
}

执行流程:

程序执行try内部的代码,判断是否有异常,

若出现异常则将异常实例化

将异常的类型与catch中的异常类型进行匹配

匹配成功后,执行异常处理代码

程序执行后续代码…..


我们去代码中学习一下try—catch—的使用:

public class Demo{
    public static void main(String[] args){
        int[] a={1,2,3};
        System.out.println("开始");
        try{
            method(a);//需要检查异常的代码
            //有异常是会实例化异常并与catch中的异常类型进行匹配,匹配成功执行异常处理代码
        }catch(ArrayIndexOutOfBoundsException e){
            System.out.println("数组下标访问错误");//异常的处理代码
        }
        System.out.println("结束");
    }
    public static void method(int[] a){
        System.out.println(a[3]);
    }
}
//输出结果
开始
数组下标访问错误
结束

通过上面的代码运行,我们看到我们捕获异常且处理后,后续的代码依然被执行了;

这也就是我们处理异常的初衷:出现异常后不影响后面代码的执行。

但是有需要我们注意的地方是异常处理代码一般来说不是输出一句话,而是要显示异常原因及位置的信息;

我们在捕获异常时实例化了对象,我们可以在异常处理代码的地方调用异常的一些方法来显示我们想要的异常原因及位置的信息。

public class Demo{
    public static void main(String[] args){
        int[] a={1,2,3};
        System.out.println("开始");
        try{
            method(a);
        }catch(ArrayIndexOutOfBoundsException e){
           // System.out.println("数组下标访问错误");//异常的处理代码
            e.printStackTrace();//我们调用了异常的方法,将错误信息打印出来
        }
        System.out.println("结束");
    }
    public static void method(int[] a){
        System.out.println(a[3]);
    }
}
//输出结果
开始
结束
java.lang.ArrayIndexOutOfBoundsException: 3
	at com.liuwei.oop.exception.demo.method(demo.java:16)
	at com.liuwei.oop.exception.demo.main(demo.java:8)

我们发现我们的异常处理得到的跟Java异常默认处理方案是一样的,但是仅仅是显示的内容一样,而我们的程序并没有终止。


1.4 Throwable的成员方法

成员方法 说明
public String getMessage() 返回此throwable的详细消息字符串
public String toString() 返回此可抛出的简短描述
public void printStackTrace() 把异常的错误信息输出在控制台

我们在代码里对三种方法进行了解:

  • public String getMessage()
public class Demo{
    public static void main(String[] args){
        int[] a={1,2,3};
        System.out.println("开始");
        try{
            method(a);
        }catch(ArrayIndexOutOfBoundsException e){
            System.out.println(e.getMessage());
        }
        System.out.println("结束");
    }
    public static void method(int[] a){
        System.out.println(a[3]);
    }
}
//输出结果
开始
3//getMessage();会返回出现异常的原因
结束

我们打开getMessage()的源代码:

发现getMessage();Throwable类的一个方法;且getMessage();中返回了一个detailMessage

detailMessageThrowable类的一个成员变量

也就是说我们调用getMessage();时返回给我们的是detailMessage,而detailMessageThrowable类的属性,也就意味着detailMessageThrowable类的有参构造中被赋值了,而这个值正是其子类自身调用构造器时传来的;

try捕捉到异常时会创建该异常的对象,此时该异常类会调用有参构造器,且调用其父类的有参构造器,其父类再去调用父类,最终调到Throwable类的有参构造器对detailMessage进行赋值,最后我们调用getMessage();并返回detailMessage的值。

//简化描述下Throwable有参的构造和getMessage()方法的调用
public class Throwable{
    private String detailMessage;
    
    public Throwable(String message){//当异常类的有参构造调动牵动了Throwable的有参构造器的调用,且将 detailMessage初始化了
        detailMessage=message;
    }
    
    public String getMessage(){
        return detailMessage;
    }
}
  • public String toString()
public class Demo{
    public static void main(String[] args){
        int[] a={1,2,3};
        System.out.println("开始");
        try{
            method(a);
        }catch(ArrayIndexOutOfBoundsException e){
            System.out.println(e.toString());
        }
        System.out.println("结束");
    }
    public static void method(int[] a){
        System.out.println(a[3]);
    }
}
//输出结果
开始
java.lang.ArrayIndexOutOfBoundsException: 3//输出了异常类型以及原因;包含了getMessage();的内容
结束
  • public void printStackTrace()
public class Demo{
    public static void main(String[] args){
        int[] a={1,2,3};
        System.out.println("开始");
        try{
            method(a);
        }catch(ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
        }
        System.out.println("结束");
    }
    public static void method(int[] a){
        System.out.println(a[3]);
    }
}
//输出结果
开始
结束
java.lang.ArrayIndexOutOfBoundsException: 3
	at com.liuwei.oop.exception.demo.method(demo.java:16)
	at com.liuwei.oop.exception.demo.main(demo.java:8)
    //输出了异常的原因,类型,位置
    //printStackTrace();输出的信息是最全的,一般我们使用这个

1.5 编译时异常和运行时异常的区别

Java中的异常有两大类:编译时异常和运行时异常;也被称为受检异常和非受检异常;

所有的RuntimeException及其子类都被成为运行时异常,除外其他的异常都为编译时异常

  • 编译时异常:必须显示处理,否则程序会发生错误,编译无法通过;
  • 运行时异常:无需显示处理,需要我们修改代码;也可以和编译时异常 一样处理
public class Demo{
    public static void main(String[] args){
        int[] a={1,2,3};
            method(a);//ArrayIndexOutOfBoundsException,需要我们修改代码
    }
    public static void method(int[] a){
        System.out.println(a[3]);
    }
}

类似我们上面的代码在编译时没有报错,而运行时报错,就是运行时异常。

编译时异常怎么处理?

也就是我们前面讲的try catch


1.6 异常处理之throws

我们前面已经知道了try catch方式来处理异常,但是并不是所有的异常情况我们都有权限通过try catch方式来处理;

Java提供了另外一种处理方式:throws处理方案

格式:

throw 异常类名;

需要注意的是throws 异常类名;是跟在方法声明的括号之后的!

throws 异常类名仅仅是抛给了调用者,并没有解决异常,如果要解决还得用try catch

//运行时异常的throws处理
public class Demo{
    public static void main(String[] args){
        //method();//此时throws将异常抛到此处,我们异常并没有解决,我们还是需要处理异常,即用try  catch方式来处理
        
        try{
            method();
        }catch(ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
        }
    }
    public static void method1() throws ArrayIndexOutOfBoundsException{//throws仅仅是向调用者抛出异常,并不能解决异常
        int[] a={1,2,3};//我们是可以通过try catch来处理的,这里演示throws处理
        System.out.println(a[3]);
    }
}

总结:

  • 编译时异常必须进行处理:两种处理方案:try catchthrows, throws要注意谁调用谁就要处理异常
  • 运行时异常可以不处理:我们始终要回来修改代码