Java 异常

155 阅读7分钟

异常

  • 异常就是代表程序出现的问题
  • 误区:不是让我们以后不出现异常,而是程序出了异常后,该如何处理

Snipaste_2022-11-17_14-17-46.png

  • Error:代表系统级别错误(属于严重问题)
    • 系统一旦出现问题,sun公司会把这些错误封装成Error对象,Error是给sun公司自己用的,不是给我们程序员用的,因此我们开发人员不用管它
  • Exception:叫做异常,代表程序可能出现的问题,我们通常会用Exception以及它的子类来封装程序出现的问题
  • 运行时异常:RuntimeException及其子类,编译阶段不会出现异常提醒,运行时出现的异常(如:数组索引越界异常)
  • 编译时异常:编译阶段就会出现异常提醒(如:日期解析异常)

Snipaste_2022-11-17_14-28-14.png

  • 为什么不将其全部归为编译时异常呢?
    • 编译阶段:Java不会运行代码,只会检查语法错误,或者做一些性能的优化
  • 为什么不将其全部归为运行时异常呢?
    • 因为编译时产生的异常更多是在于提醒程序员检查本地信息
    • 而运行时异常主要是代码出错而导致程序出现的问题,一般是由于参数传递错误带来的问题

异常的作用

  • 异常是用来查询bug的关键参考信息
  • 异常可以作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况

异常的处理方式

JVM默认的处理方式

  • 把异常的名称,异常原因及异常的位置等信息输出在了控制台
  • 程序停止运行,下面的代码不会再执行了

自己处理(捕获异常)

  • 格式
try {
    可能出现异常的代码;
} catch(异常类名 变量名) {
    异常的处理代码;
}
  • 目的:当异常出现异常时,可以让程序继续往下执行
int[] arr = {1, 2, 3, 4, 5, 6};

try {
    //可能出现异常的代码
    System.out.println(arr[10]);
} catch(ArrayIndexOutOfBoundsException e) {
    //如果出现了ArrayIndexOutOfBoundsException异常,我该如何处理
    System.out.println("索引越界了");
}

System.out.println("看看我执行了吗");

  • 代码逻辑

    • 此处出现了异常,程序就会在这里创建一个ArrayIndexOutOfBoundsException对象
    • new ArrayIndexOutOfBoundsException();
    • 拿着这个对象到catch的小括号中对比,看括号中的变量是否可以接收这个对象
    • 如果能被接收,就表示该异常被捕获,执行catch里面对应的代码
    • 当catch里面所有的代码执行完毕,继续执行try...catch体系下面的其他代码
  • 如果try中没有遇到问题,怎么执行

    • 会将try里面所有的代码全部执行完毕,不会执行catch里面的代码
    • 只有当出现了异常,才会执行catch里面的代码
  • 如果try中可能会遇到多个问题,怎么执行

    • 会写多个catch与之对应
    • 如果我们要捕获多个异常,这些异常中存在父子关系的话,那么父类一定要写在下面
int arr = {1, 2, 3, 4, 5, 6};
try {
    System.out.println(arr[10]);
    System.out.println(2 / 0);
    String s = null;
    System.out.println(s.equals("abc"));
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("索引越界");
} catch (ArithmeticException e) {
    System.out.println("除数不能为0");
} catch (NullPointerException e) {
    System.out.println("空指针异常");
} catch (Exception e) {
    System.out.println("Exception");
}
)
  • JDK7的新的书写方式,当多个异常的处理方式一样时,我们可以在catch里面捕获多个异常,中间用 | 进行隔开,表示如果出现了A异常或者B异常的话,采取同一种处理方案
try {
    System.out.println(arr[10]);
    System.out.println(2 / 0);
} catch (ArrayIndexOutOfBoundsException | ArithmeticException e) {
    System.out.println("Exception");
}
  • 如果try中遇到的问题没有被捕获,怎么执行
    • 相当于try...catch的代码白写了,最终还是会交给虚拟机进行默认处理
  • 如果try中遇到了问题,那么try下面的代码还会执行吗
    • 下面的代码就不会执行了,直接跳转到对应的catch当中,执行catch里面的语句体
    • 但是如果没有对应的catch与之对应,那么还是会交给虚拟机进行处理

抛出处理

throws

  • 写在方法定义处,表示声明一个异常,告诉调用者,使用本方法可能会有那些异常
public void 方法()throws 异常类名1, 异常类名2...{
    ...
}
  • 编译时异常:必须要写
  • 运行时异常:可以不写

throw

  • 写在方法内,结束方法,手动抛出异常对象,交给调用者,方法中下面的代码不再执行了
public void 方法(){
    throw new NullPointerException();
}
//定义一个方法求数组的最大值
int[] arr = {};
int max = 0;
//在这里如果真的出现异常了,还什么都没有写,那么异常会有虚拟机自行处理,后面的代码不会运行,为了让后面的代码能够正常运行,建议还是手动捕获异常
try {
    max = getMax(arr);
} catch (NullPointerException e) {
    System.out.println("空指针异常");
} catch (ArrayIndexOutOfException e) {
    System.out.println("索引越界异常");
}

System.out.println(max);

                                    //这里的两个异常都是运行时异常,可以不写

public static int getMax(int[] arr) throws NullPointerException, ArrayIndexOutOfException{
    if (arr == null) {
        //手动创建一个异常对象,并把这个对象交给方法的调用者处理
        //此时方法就会结束,方法下面的代码不会再执行了
        throw new NullPointerException();
    }
    if (arr.length == 0) {
        throw new ArrayIndexOutOfException();
    }
    
    int max = arr[0];
    for (int i = 1; i < arr.length; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
    }
    return max;
}

总结

处理方式特点
虚拟机默认处理异常的方式把异常信息一红色字体打印再控制台,并结束程序
捕获:try...catch一般用在调用处,能让代码继续往下运行
抛出:throws throw在方法中,出现异常了 方法就没有继续运行下去的意义了,采取抛出处理 让该方法结束运行并告诉调用者出现了问题

异常中的常见方法

Throwable的成员方法

方法名称说明
public String getMessage()返回此throwable的详细消息字符串(异常的信息)
public String toString()返回此可抛出的简短描述(异常的名字和异常的信息)
public void printStackTrace()把异常的错误信息输出在控制台(异常的名字、信息和位置),仅仅是打印信息,不会停止程序的运行
  • 为什么是输出是红字
    • 正常的输出语句
      • System.out.println(123);
    • 错误的输出语句(用来打印错误信息)
      • System.err.println(123);
    • 如果正常和错误的语句在一起输出时,顺序可能会不一样,和多线程的知识有关

自定义异常

  • 意义:就是为了让控制台的报错信息更加见名知意

步骤

  • 定义异常类
    • 见名知意
  • 写继承关系
    • 运行时:继承RuntimeException
    • 编译时:继承Exception
  • 空参构造
    • alt + insert 第一个
  • 带参构造
    • alt + insert 第二个

练习

  • 键盘录入自己心仪的女朋友姓名和年龄,姓名的长度在3-10之间,年龄的范围在18-40之间,超出这个范围是异常数据不能赋值,需要重新录入,一直录到正确为止,需要考虑用户在键盘录入时的所有情况,比如:录入年龄时超出范围,录入年龄时录入了abc等情况
//javabean类
public class GirlFriend {
    private int age;
    private String name;

    public GirlFriend() {
    }

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

    public int getAge() {
        return age;
    }
                                //运行时异常,可写可不写
    public void setAge(int age) throws AgeOutOfAgeException{
        if (age < 18 || age > 40) {
            throw new AgeOutOfAgeException(age + "年龄超出范围");
        }
        this.age = age;
    }

    public String getName() {
        return name;
    }
                                     //运行时异常,可写可不写
    public void setName(String name) throws NameFormatException{
        if (name.length() < 3 || name.length() > 10) {
            throw new NameFormatException(name + "长度超出范围");
        }
        this.name = name;
    }

    public String toString() {
        return "GirlFriend{age = " + age + ", name = " + name + "}";
    }
}

//两个自定义异常
public class AgeOutOfAgeException extends RuntimeException{
    public AgeOutOfAgeException() {
    }

    public AgeOutOfAgeException(String message) {
        super(message);
    }
}

public class NameFormatException extends RuntimeException{
    public NameFormatException() {
    }

    public NameFormatException(String message) {
        super(message);
    }
}

while (true) {
    try {
        System.out.println("请输入姓名");
        String nameStr = sc.nextLine();
        gf.setName(nameStr);

        System.out.println("请输入年龄");
        String ageStr = sc.nextLine();
        int age = Integer.parseInt(ageStr);
        gf.setAge(age);

        break;
    } catch (NumberFormatException e) {
        e.printStackTrace();
        continue;  //可写可不写
    } catch (AgeOutOfAgeException e) {
        e.printStackTrace();
        continue;  //可写可不写
    } catch (NameFormatException e) {
        e.printStackTrace();
        continue;  //可写可不写
    }
}
System.out.println(gf);