Java的”异常“超详细讲解

Java的”异常“超详细讲解

前言:

​ 我们在写代码的时候,经常会遇到错误,并且编译器会给出相关提示 如:

image-20231128100333444

这里就会显示 ArrayIndexOutOfBoundsException *(数组越界)*错误。并且会提示报错的行号。

同时我们发现 ArrayIndexOutOfBoundsException 是可以点击进去的,点击进去之后会发现是一个JDK自带的类

image-20231128100814146

由此我们可以知道:

在Java中,不同类型的异常,都有与其对应的类来进行描述,也可以说异常也是一种类

但是我们在日常编写项目时,错误是时常发生的。我们作为程序员当然不希望一个项目时不时的崩溃。

在此之前,我们通常对代码可能出现的潜在问题用 条件判断来进行处理:,下面我以一个简略的游戏进行演示:

image-20231128105536137

可以看到当条件一旦增加,代码就会非常臃肿,并且难以后续对功能进行增加和修改。

所以我们必须要学会如何使用,以及处理异常的方式!!


一、异常的分类:

image-20231128102242931

  1. Throwable
    是异常体系的顶层类,其派生出两个重要的子类, ErrorException

  2. Error
    **指的是Java虚拟机无法解决的严重问题,**比如:JVM的内部错误、资源耗尽等,
    典型代表:StackOverflowError(栈溢出错误)和OutOfMemoryError

  3. Exception:
    异常产生后程序员可以通过代码进行处理,使程序继续执行。而异常又分为运行时异常和编译时异常;

    1. 编译时异常:
      编译无法通过,编译器会进行提醒。也称为受检查异常(Checked Exception)
    2. 运行时异常:
      可以通过编译,但是运行后会报错。也称为非受检查异常(Unchecked Exception)
      常见的 NullPointerException(空指针异常)、ArrayIndexOutOfBoundsException(数组越界异常)都属于运行时异常。

二、异常的处理:

1.异常的抛出:

在编写程序时,如果程序中出现错误,此时就需要将错误的信息告知给调用者

关键字:throw

语法:throw new 异常类型(要告知的信息);

代码案例:

public static void func(int[] arrays) {
    //如果传递的是一个空数组,抛出异常
    if(arrays == null) {
        //这里的NullPointerException是Java自带的一个类
        throw new NullPointerException("你传递了一个空指针");
    }
}
public static void main(String[] args) {
    int[] arrays = null;
    func(arrays);
}

运行结果:

image-20231128104202892

注意事项

  1. 异常抛出 必须写在方法内部!!!(由方法来实现抛出)
  2. 抛出对象必须是Exception或是其子类
  3. 默认情况下:RuntimeException及其子类可以通过JVM处理
  4. 异常一旦抛出,其后的代码就不会执行

image-20231128104608590

我们可以看到,在IDEA中,如果异常后还有代码,会直接提示编译错误(因为无法被执行!)

2. 异常声明:

关键字: throws

语法格式:

修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2…{

}

如果我们在使用方法是,有编译时异常(编译器警告),可以使用throws来声明这个异常可能会存在,这样就可以通过编译时异常了。

下面我以clone方法为例子:如果不对main函数进行异常声明,会提示clone编译错误(无法确定能否被克隆)

image-20231128111035603

我们在main函数(使用者)后加入声明:throws CloneNotSupportedException

发现编译错误消失,可以正常运行:image-20231128111126989

class Student implements Cloneable{
    public String name;
    public int age;

    //构造方法:
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class CloneTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        Student s1 = new Student("张三",18);
        Student s2 = (Student) s1.clone();
    }
}

注意事项

  1. throws必须跟在方法的参数列表之后

  2. 声明的异常必须是 Exception 或者 Exception 的子类

  3. 方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型

    具有父子关系,直接声明父类即可。

  4. 调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出

3.异常捕捉:

关键字:try - catch

语法格式:

try{

​ // 将可能出现异常的代码放在这里

​ }catch(要捕获的异常类型 e){

​ //异常处理内容

​ }[catch(异常类型 e){

​ // 对异常进行处理

​ }finally{

​ // 此处代码一定会被执行到

​ }]

解释:

  1. try { }:
    内部放的是可能出现异常的代码块(也有可能没有异常)
    如果出现异常,则异常后续的代码均不会被执行,直接跳转到对应异常类的的catch内容

  1. catch( ){ }

    1. ()小括号内是异常类类名 + 实例化的对象名(这里通常习惯用e表示)
    2. { } 花括号内表示的对应的异常处理内容
    3. 可以存在多个catch
    4. 也可以一个catch内写多个异常类型,只需要一个实例化对象名,中间用 | 隔开image-20231128112515068
      (不推荐用此写法,无法判断异常类型!!)

  2. finally{ }
    finally{}内的代码块,无论异常发生与否,始终会被执行
    通常被用于资源的关闭,对资源进行回收。

注意事项:

  1. try块内抛出异常位置之后的代码将不会被执行

  2. 如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到

    JVM收到后中断程序----异常是按照类型来捕获的

  3. try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获

  4. 如果异常存在父子类关系,则必须子类在前(catch),父类在后(catch)
    因为如果父类在前,子类内容无法被执行,肯定会被父类捕获(catch)

image-20231128113904417
这里就是因为父类 RuntimeException在前,后续的InputMismatchException无法被执行,出现编译错误。 子类在前则只会抛出子类的异常,而不会抛出父类的异常。

  1. finally中的代码一定会执行的,且是最后执行。一般在finally中进行一些资源清理的扫尾工作
   public static int func3() {
       Scanner scanner = new Scanner(System.in);
       try{
           int a = scanner.nextInt();
           return a;
       } catch (InputMismatchException e) {
           //InputMismatchException是错误输入类型异常
           // (这里我想要输入的是int,但是输入String就会报此错误)
           e.printStackTrace();
           System.out.println("发生了InputMismatchException异常");
   
       }finally {
           scanner.close();
           System.out.println("Scanner资源已经关闭!!!");
           return 100;
       }
   
   }
   
   public static void main(String[] args) {
   
       int ret = func3();
       System.out.println(ret);
   }

注意这里,try语句中已经有了return 语句了, 正常情况下输出的结果是你输入的数;

我们进行输入测试:

在这里插入图片描述

我们会发现最终return的值是100,即为finally return的值,

这说明:finally不仅一定会执行,而且可以在return之后最后执行。


三、自定义异常类:

既然异常是一个类,那我们肯定也能通过定义类,来定义一个自定义的异常类型

具体如何定义呢 ?,我们可以仿照已知的异常类,这里是NullPointerException为例子:

image-20231128115009842

定义方法为

  1. 继承RuntimeException类(异常父类) 或者是 Exception类。
  2. 重写构造方法并调用父类的构造,用来传递发生错误的字段

下面我以一个简单的登录代码来进行演示:

首先是 登录功能类:

public class Login {

    //这里是字段:
    public String userName;
    public String passWord;

    //进行简单的逻辑判断
    public void login(String userName,String passWord) {

        //如果输入的信息不匹配该类的成员信息,抛出自定义异常!(这里只抛出,不解决)
        if(!this.userName.equals(userName)) {
            throw new UserNameException("账户名输入错误!!!");
        }
        if(!this.passWord.equals(passWord)) {
            throw new PassWordException("密码输入错误!!!");
        }
    }

    //构造函数

    public Login(String userName, String passWord) {
        this.userName = userName;
        this.passWord = passWord;
    }
}

自定义类

首先是用户名错误的异常类:

public class UserNameException extends RuntimeException {
    public UserNameException() {
        super();
    }

    public UserNameException(String userName) {
        super(userName);
    }
}

其次是密码错误类:

public class PassWordException extends RuntimeException{

    public PassWordException() {
    }

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

在主函数中实例化:

public class Main {

    public static void main(String[] args) {

        try {
            Login login = new Login("admin","123123");
            login.login("admin","123");
        }catch (UserNameException e) {
            //打印函数栈信息
            e.printStackTrace();
        }catch (PassWordException e) {
            //打印函数栈信息
            e.printStackTrace();
        }finally {
            System.out.println("finally被执行,程序继续...");
        }

    }
}

输出结果:显示了自定义的异常类型 PassWordException

image-20231128115425321


四、 总结:

异常的处理流程:

image-20231128115602864

  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值