异常

发布时间 2023-09-09 11:54:27作者: SimpleWord
title: 异常
index_img: https://tuchuangs.com/imgs/2023/08/10/6e572ca2666c699d.png
tags:
  - Java SE
categories:
  - Java SE
excerpt: 异常处理

异常

异常继承体系

  • Error系统级错误(不是异常)

  • Exception程序可能出现的问题(不是错误),是异常的父类

  • RuntimeException异常的子类,运行时才会出现

  • 其它异常:异常的子类,编译时异常

    • 必须在编译时期处理

异常的作用

  • 查询bug的关键参考信息
  • 异常可以作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况【相比直接打印结果,可以知道出错在哪一行,出现了说明问题】

异常处理

JVM默认异常处理

  • 异常的名称、位置、原因等信息输出到控制台。
  • 停止程序(后续代码不再执行)
public class Main {
    public static void main(String[] args) {
        System.out.println("我是测试语句1");
        System.out.println(1 / 0);
        System.out.println("看不见我");
    }
}

image-20230809213416448

try…catch捕获异常

这种方式,当出现异常,程序可以继续执行

用在调用处

格式

jdk7+支持catch中写多个异常(用|),如果多种异常处理相同,可以使用

try{
  可能出现的异常【注意可能】;
}catch(异常类型 变量名){
  异常处理代码;
}

执行流程

  • 没有异常,不会执行catch的语句
public class Main {
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9};

        //没有异常
        try {
            System.out.println("正常执行");
        } catch (AbstractMethodError e) {
            System.out.println("数组越界"); //不会执行
        }
        System.out.println("看得见我吗3?");
    }
}

image-20230809221924001

  • 出现异常,该处产生一个相关异常对象【如数组越界】
  • catch中的异常类型变量如果可以接收上方产生的异常(范围大于等于)
    • 可以被接收,然后执行catch中的代码
    • catch中代码执行结束,直接执行try…catch外的代码【不会返回try了】
  • 一次只能产生一个异常,匹配一个catch
public class Main {
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9};
        //产生ArrayIndexOutOfBoundsException异常且可以捕获
        try {
            System.out.println("我正常执行");
            System.out.println(arr[10]);
            System.out.println("看不见我"); //不会执行
        } catch (Exception e) {
            System.out.println("数组越界"); 
        }
        System.out.println("看得见我吗?");
    }
}

image-20230809215929328

如果可能产生多个异常,可以写多个catch,但父类异常类型应该写在最后

public class Main {
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9};

        try {
            System.out.println("我正常执行");
            System.out.println(arr[10]);
            System.out.println(1 / 0);
            System.out.println(Integer.parseInt("java"));
        } catch (NumberFormatException e) {
            e.printStackTrace();
            System.out.println("字符串格式不符合");
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
            System.out.println("数组越界异常");
        } catch (ArithmeticException e) {
            e.printStackTrace();
            System.out.println("算数异常");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("运行时异常");
        }
        System.out.println("看得见我吗?");
    }
}

image-20230809234144297

  • catch捕获不到异常,不会执行其中的代码
    • 异常不存在,正常执行
    • 异常范围不符,产生了但捕获不到,会交由JVM处理【控制台+终止】
public class Main {
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9};
        //产生ArrayIndexOutOfBoundsException异常,但不可捕获
        try {
            System.out.println("正常执行");
            System.out.println(arr[10]);
        } catch (AbstractMethodError e) { //不匹配的异常,jvm处理
            System.out.println("数组越界");
        }
        System.out.println("看得见我吗3?"); ///不会执行
    }
}

image-20230809221654603

Throable提供的方法

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

选其一即可:一般选择printStackTrace(),包含的信息最多。

public class Main {
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9};

        //可捕获数组越界异常
        try {
            System.out.println(arr[10]);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("throwable的详细消息字符串:" + e.getMessage());
            System.out.println("异常简短信息:" + e.toString());
            e.printStackTrace(); //将异常错误信息打印在控制台
        }
        System.out.println("看得见我吗3?");
    }
}

image-20230809223131340

抛出异常

throws

  • 用在方法定义处,声明一个异常,本方法可能产生的异常
  • 编译异常必须要写,运行时异常可以不写
public void 方法() throws 异常类型1,异常类型2....{
  ...
}

throw

  • 用在方法内,结束方法
  • 手动抛出异常对象,交给调用者,后面的代码不再执行【虚拟机接管了异常】
public void 方法(){
  throw new NullpointerException();
}

案例

public class Main {
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9};
        int[] arr1 = null;
        int[] arr2 = {};
        getMax(arr);
        System.out.println("----------");
        getMax(arr1);
        System.out.println("----------"); //不会执行,上一步throw产生了异常,下面的都不会执行了
        getMax(arr2);
    }

    // NullPointerException,ArrayIndexOutOfBoundsException是运行时异常,对于throws我们可以不用写
    public static int getMax(int[] arr) /*throws NullPointerException,ArrayIndexOutOfBoundsException*/ {
        if (arr == null) {
            throw new NullPointerException("传入数组是null"); //空指针异常
        }
        if (arr.length == 0) {
            throw new ArrayIndexOutOfBoundsException("传入数组长度为0"); //数组越界异常
        }
        System.out.println("如果异常有效我就看不见了");

        int max = arr[0];
        for (int i = 0; i < arr.length; i++) {
            if (max < arr[i]) {
                max = arr[i];
            }
        }
        return max;
    }
}

image-20230809230621864

抛出+捕获处理

在上一个案例,我们抛出异常,交由JVM默认处理异常【停止虚拟机执行后续代码】,接下来我们要try…catch自行捕获处理,让后续代码可以执行。

public class Main {
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9};
        int[] arr1 = null;
        int[] arr2 = {};

        System.out.println(handleException(arr));  // 输出9
        System.out.println(handleException(arr1)); // 输出"传入数组是null",返回-1
        System.out.println(handleException(arr2)); // 输出"传入数组长度为0",返回-1
    }

    public static int handleException(int[] arr) { //提取异常处理方法
        try {
            return getMax(arr);
        } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
            System.out.println(e.toString()); // 打印出异常的名字和详细信息
            return -1; // 返回一个默认值
        }
    }

    public static int getMax(int[] arr) {
        if (arr == null) {
            throw new NullPointerException("传入数组是null"); //包含具体的错误信息
        }
        if (arr.length == 0) {
            throw new ArrayIndexOutOfBoundsException("传入数组长度为0"); //包含具体的错误信息
        }
        int max = arr[0];
        for (int j : arr) {
            if (max < j) {
                max = j;
            }
        }
        return max;
    }
}

image-20230809231321304

自定义异常

问题

java提供的异常不满足我们的需求,怎么办?

public class Friend {
    String name;
    int age;

    public Friend() {
    }

    public Friend(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { 
        return name;
    }

    public void setName(String name) { //这里产生的异常在java中没有描述,只好用运行时异常,方法内用throw
      if(name.length()>5){
            throw new RuntimeException();
        }
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) { //这里产生的异常在java中没有描述,只好用运行时异常,throws可以不用(运行时异常可省略)
        if(age<18 || age>40){
            throw new RuntimeException();
        }
        this.age = age;
    }

    public String toString() {
        return "Friend{name = " + name + ", age = " + age + "}";
    }
}
public class Main {
    public static void main(String[] args) {
        Friend friend = new Friend();
        Scanner sc = new Scanner(System.in);

        while (true) { //使用循环产生异常,重写输入
            try {                                            //try...catch捕获异常
                System.out.println("输入你想要的名字:");
                String name = sc.nextLine();
                friend.setName(name); //可能异常

                System.out.println("输入你期望的年龄:");
                String s = sc.nextLine();
                int age = Integer.parseInt(s); //可能异常
                friend.setAge(age); //可能数字格式化异常

                break; //输入正确,跳出循环

            } catch (NumberFormatException e) {   //多张可能异常,可以多写几个catch,父类异常类型在最后
                System.out.println("年龄格式错误");
            } catch (RuntimeException e) {
                System.out.println("姓名或年龄范围错误");
            }
        }
    }
}

image-20230810000351165

书写自定义异常

  • 定义异常类
  • 写继承关系
    • 运行时异常继承RunTimeException【对于上方问日可以继承运行时异常】
    • 编译时异常继承Exception
  • 写空参构造
  • 带参构造
public class NameException extends RuntimeException {
    public NameException() {
    }

    public NameException(String message) {
        super(message);
    }
}
public class AgeException extends RuntimeException {
    public AgeException() {
    }

    public AgeException(String message) {
        super(message);
    }
}
public class Friend {
    String name;
    int age;

    public Friend() {
    }

    public Friend(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        if (name.length() > 5) {
            throw new NameException("ameException:姓名太长"); //注意
        }
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age < 18 || age > 40) {
            throw new AgeException("AgeException:年龄不在范围"); //注意
        }
        this.age = age;
    }

    public String toString() {
        return "Friend{name = " + name + ", age = " + age + "}";
    }
}
public class Main {
    public static void main(String[] args) {
        Friend friend = new Friend();
        Scanner sc = new Scanner(System.in);

        while (true) { //使用循环产生异常,重写输入
            try {
                System.out.println("输入你想要的名字:");
                String name = sc.nextLine();
                friend.setName(name); //可能异常

                System.out.println("输入你期望的年龄:");
                String s = sc.nextLine();
                int age = Integer.parseInt(s); //可能异常
                friend.setAge(age); //可能数字格式化异常

                break; //输入正确,跳出循环

            } catch (NumberFormatException e) {
                System.out.println("年龄格式错误");
            } catch (AgeException e) {  //年龄异常
                e.printStackTrace();
            } catch (NameException e) { //名字异常
                e.printStackTrace();
            }
        }
    }
}

image-20230810000026113