4. Java入门之异常是什么?如何自定义异常?异常的分类、产生及处理(小白教程)

目录

    • 1. 异常的概述与体系
      • 1.1 异常的概述
      • 1.2 异常的体系及分类
    • 2. 异常的产生及处理
      • 2.1 异常的产生
      • 2.2 throws声明异常
      • 2.3 try...catch捕获异常
      • 2.4 Throwable中3个异常处理的方法
    • 3. 异常的注意事项及自定义异常
      • 3.1 多异常的捕获处理
      • 3.2 finally代码块
      • 3.3 子父类异常
      • 3.4 自定义异常

1. 异常的概述与体系

1.1 异常的概述

异常,就是不正常的意思,在程序中的意思是:在程序执行过程中,出现的非正常情况,最终会导致JVM的非正常停止

Java等面向对象的编程语言当中,异常本身就是一个,产生异常就是创建异常对象并抛出Java(或者说JVM)处理异常的方式是中断处理

注意:异常指的不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行

1.2 异常的体系及分类

异常机制其实是帮助我们找到程序中的问题,异常的根类java.lang.Throwable,其下有两个子类java.lang.Errorjava.lang.Exception所有的异常都是由 Throwable类继承而来的,为了更清晰表达他们的层次关系,下面给出一张图
4. Java入门之异常是什么?如何自定义异常?异常的分类、产生及处理(小白教程)_第1张图片
其中 Error 类描述了Java运行时系统的内部错误与资源耗尽错误,这是我们攻城狮所不能处理、只能尽量避免的;而我们这里所谈论的异常,是指 Exception 类,这是由于使用不当导致,是可以避免的

在设计Java程序时,要重点关注 Exception 类,该类还可以分为两个分支, RuntimeException 与其他异常例如 IOException 等,一般的规则是:由编程错误导致的异常属于 RuntimeException;如果程序本身没有问题,但由于I/O错误这类问题导致的异常属于其他异常

要注意的是RuntimeException 可以处理也可以不处理,一般不处理;而其他异常例如 IOException 等则必须处理

2. 异常的产生及处理

2.1 异常的产生

关于异常的产生,我们来举个栗子,下面是演示程序代码

public class Demo {
    public static void main(String[] args) {
        int [] arr = {1, 2, 3};
        int e = get(arr,3);
        System.out.println(e);
    }
    private static int get(int[] arr,int index) {
        int element = arr[index];
        return element;
    }
}

main方法调用了get方法访问数组中索引为3的值,但数组的长度是3,索引是0-2,因此JVM就会检测出程序会出现异常情况,这时候JVM就会做两件事情:

  • JVM会根据异常产生的原因创建一个异常对象,这个异常对象包含了异常产生的内容、原因与位置
    new ArrayIndexOutOfBoundsException("Index 3 out of bounds for length 3");

  • get方法中,没有异常处理的逻辑(即try...catch),那么JVM就会把异常对象抛出给该方法的调用者main方法来处理这个异常

main方法收到了这个异常对象,但是main方法也没有异常处理的逻辑,因此他会继续把异常对象抛出main方法的调用者JVM处理

JVM接收到了这个异常对象,做了两件事情:

  • 把异常对象以红色的字体打印在控制台
  • JVM会终止当前正在执行的Java程序,即中断处理

2.2 throws声明异常

上面简单举例介绍了异常的产生过程,那么怎么来处理这个异常呢,方法一为本小节所介绍的 throws声明

首先来介绍一下throw关键字(注意这个是throw不是throws
throw的作用是用于在方法中抛出指定的异常,一般只在自定义异常时使用,此处仅简单介绍以免引起混淆,详细使用方法将在自定义异常介绍

接下来就介绍throws关键字
当成员方法内部抛出了一个异常的时候,我们必须把这个异常处理,那么我们可以使用throws关键字处理异常对象,把该异常对象进行声明,他就会抛出给方法的调用者处理(说白了就是自己不处理,给别人处理),最终是交给JVM处理,也就是中断处理,他的基本格式如下:
4. Java入门之异常是什么?如何自定义异常?异常的分类、产生及处理(小白教程)_第2张图片

有几个注意事项是需要关注的:
throws关键字必须写在方法声明处,即

修饰符 返回值类型 方法名(参数列表) throws XxxxException{}

throws关键字后声明的异常必须是Exception或者是Exception的子类
方法内部如果出现了多个异常对象,那么throws后也必须声明多个异常(如果抛出异常对象有父子关系,则只声明父类异常即可
如果调用了一个声明异常的方法,那么该方法必须继续对该异常进行处理,可以继续使用throws声明,使用下一小节的try...catch自己处理异常

2.3 try…catch捕获异常

上一节介绍了处理异常的一种方法,这里介绍处理异常的另一种方法。前面说了使用throws处理异常就是自己不处理,给别人处理,那么try...catch则刚好相反,该种处理方式为自己处理异常

首先给出一个格式

try{
       可能会产生异常的代码
} catch(异常类名,变量名){
       异常的处理逻辑,出现异常之后,怎样处理异常对象
       一般在工作中会把该异常信息写入日志当中
}

下面说一下注意事项
try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常
如果try中产生了异常,那么就执行catch对应的异常处理逻辑,执行完catch中的处理逻辑后,继续执行try...catch后的代码
如果try中没有产生了异常,那么就不会执行catch中对应的异常处理逻辑,执行完try中的代码后,继续执行try...catch后的代码

2.4 Throwable中3个异常处理的方法

Throwable 是所有异常的父类,在该类中定义了3个异常处理的方法
String getMessage():返回此异常的简短描述
在这里插入图片描述
String toString():返回此异常的详细描述
在这里插入图片描述
void printStackTrace()JVM打印该异常对象,默认此方法,打印的异常信息是最全面的
在这里插入图片描述

3. 异常的注意事项及自定义异常

3.1 多异常的捕获处理

当存在多个异常的时候使用捕获try...catch)如何处理呢?有三种处理方法:.

多个异常分别处理
该种方法,说白了就是存在多少个异常就写多少个try...catch,一种异常使用一组try...catch来处理

public class Demo {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        List<Integer> list = List.of(1, 2, 3);
        try{
            System.out.println(arr[3]);
        }catch (ArrayIndexOutOfBoundsException e){
            System.out.println(e);
        }
        try{
            System.out.println(list.get(3));
        }catch (IndexOutOfBoundsException e){
            System.out.println(e);
        }
        System.out.println("后续代码");
    }
}

多个异常一次捕获多次处理
该种方法,简单来说就是只写一个try,把所有有可能出现异常的代码都放进去try中,然后写若干个catch,一个catch对应一种异常

public class Demo {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        List<Integer> list = List.of(1, 2, 3);
        try{
            System.out.println(arr[3]);
            System.out.println(list.get(3));
        }catch (ArrayIndexOutOfBoundsException e){
            System.out.println(e);
        }catch (IndexOutOfBoundsException e){
            System.out.println(e);
        }
        System.out.println("后续代码");
    }
}

但是该方法有一个注意事项
catch中定义的异常变量,如果有父子类关系,那么子类的异常变量必须写在上边,否则会报错。其原理是,若出现异常,则会从catch中顺序查找可以赋值的异常变量,倘若父类异常放在上边,子类异常可以用多态的形式进入这个catch那么写在父类catch后的子类catch相当于没用,因此必须子类写在父类前

多个异常一次捕获一次处理
该种方法,其形式同样是只有一组try...catch,把所有可能产生异常的代码都放进try中,然后catch中的异常变量写一个无论try中出现哪种异常都能接收的异常类型,即可以是try中所出现异常类型的共同父类

public class Demo {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        List<Integer> list = List.of(1, 2, 3);
        try{
            System.out.println(arr[3]);
            System.out.println(list.get(3));
        }catch (Exception e){
            System.out.println(e);
        }
    }
}

3.2 finally代码块

finally代码块是跟try...catch一起使用的,在finally代码块中的代码,无论是否出现异常,他都会被执行。为什么需要这种操作呢?因为try...catch的处理逻辑是,当try中的某行代码检测出异常,那么该行代码的后续代码就不会被执行,直接跳转catch中,那么我又必须使用那行代码怎么办呢?这时候可以放进去finally代码块中,下面举个例子

public class Demo {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try{
            System.out.println(arr[3]);
            System.out.println("资源释放");
        }catch (Exception e){
            System.out.println(e);
        }
        System.out.println("后续代码");
    }
}

假如我需要无论是否产生异常,打印输出“资源释放”,那么可以把代码改成:

public class Demo {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try{
            System.out.println(arr[3]);
        }catch (Exception e){
            System.out.println(e);
        }finally {
            System.out.println("资源释放");
        }
        System.out.println("后续代码");
    }
}

需要值得注意的是:
finally代码块不能单独使用,必须和try...catch一起使用
finally一般用于资源释放,无论程序是否异常,最后都要资源释放(IO)
如果finally中有return语句,则永远返回finally中的结果,应该避免发生该情况

下面来解释一下第点,例如我的程序是这样子的:

public class Demo {
    public static void main(String[] args) {
        int a = getA();
        System.out.println(a);
    }

    private static int getA() {
        int a = 3;
        try{
            return a;
        }catch (Exception e){
            System.out.println(e);
        }finally {
            return 10;
        }
    }
}

显然这里我们想要的是不异常返回3,但由于finally代码块中也有return语句,因此无论有没有产生异常都会返回10

3.3 子父类异常

这里先简单给出子父类异常的使用规则

  • 如果父类抛出了多个异常,那么子类重写父类方法时,应该要抛出和父类相同的异常,或者是父类异常的子类或者不抛出异常
  • 如果父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常,此时子类产生的异常,只能捕获处理,不能声明抛出

给出简单的栗子

public class Fu {
    public void method01() throws NullPointerException,ClassCastException{}
    public void method02() throws IndexOutOfBoundsException{}
    public void method03() throws ArrayIndexOutOfBoundsException{}
    public void method04() {}
}
public class Zi extends Fu{
    public void method01() throws NullPointerException,ClassCastException{/*子类抛出与父类相同的异常*/}
    public void method02() throws ArrayIndexOutOfBoundsException{/*子类可抛出父类异常的子类*/}
    public void method03() {/*父类抛出异常,子类可以不抛出*/}
    public void method04() {/*因父类没有抛出异常,此处若有异常只能try...catch处理,不能throws*/}
}

3.4 自定义异常

关于异常的种类,官方的JDK中给我们定义了很多异常类,但是,往往在项目开发中,这些异常类是不够用或者不能满足我们的设计要求的,这时候就需要自定义一些异常类以供使用,其定义的格式与定义子类格式类似:

public class XxxxException extends Exception或者RuntimeException{
    //添加一个空参构造方法
    //添加一个带异常参数构造方法
}

注意

  • 自定义异常类一般都是以Exception结尾,说明该类时一个异常类
  • 自定义异常类必须继承ExceptionRuntimeException
    若继承Exception,那么该自定义异常类就是一个编译期异常,如果方法内部抛出了编译期异常,那么必须处理,要么throws,要么try...catch
    若继承RuntimeException,那么该自定义异常类就是一个运行期异常,无需处理,交给JVM处理(中断处理)

查看ArrayIndexOutOfBoundsException源码我们可以发现,它的构造方法均调用了父类的构造方法,因此我们在自定义异常时也可以调用父类的构造方法,让父类来处理这个异常信息
4. Java入门之异常是什么?如何自定义异常?异常的分类、产生及处理(小白教程)_第3张图片

你可能感兴趣的:(Java入门)