• Java异常


    1. Java异常的体系结构
      ①在Java当中,Throwable是异常体系的顶层类,其派生出2个子类,一个是错误Error,一个是异常Exception。
      ②错误Error是指Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等。代表:StackOverFlowError栈溢出错误,这是是逻辑上的错误,必须由程序员排查代码。
      ③异常Exception分为 编译时的异常(受查异常)和运行时的异常(非受查异常、RuntimeException),所有的运行时异常都会继承RuntimeException类。
      ④异常产生后程序员可以通过代码处理,使程序继续执行。
      ⑤编译时出现的语法错误,不能称之为异常,比如:System.out.println写成了system.out.println。
      在这里插入图片描述
    2. 异常的处理
      异常的处理通常有2种方法
      ①事前防御型:LBYL:Look Before You Leap. 在操作之前就充分检查。做一步检查一步。
      事后认错型:EAFP:It’s Easier to Ask Forgivness than Permission. 事后获取原谅比事前获取许可更容易。也就是先操作,遇到问题再处理。
    try{
    //一些列操作
    }catch(捕获操作可能产生的异常类型 变量){
    //处理异常
    }catch(捕获操作可能产生的异常类型 变量){
    //处理异常
    }catch(捕获操作可能产生的异常类型 变量){
    //处理异常
    }
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    优势:正常流程和错误流程分开,程序员更关注正常流程,代码也更清晰,异常处理的核心思想就是EAFP

    1. 要想处理异常,首先要学会抛出异常,抛出异常的关键字为:throw。
      在Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。
      注意:throw必须写在方法体内部,并且throw抛出的对象必须是Exception或者Exception的子类。
      ①抛出运行时异常(非受查异常)RuntimeException,可以不用处理,直接交给JVM来处理。
    public class Test {
        public static void main(String[] args) {
            func(0);
        }
        
        public static void func(int a) {
            if (a == 0) {
                throw new NullPointerException();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    运行结果:
    在这里插入图片描述
    分析:一般在运行结果中提示行数的最上面一行(本例中的第8行)就是我们需要解决的,一般的当解决这个异常后,下面的异常行数就也随之解决了。
    ②抛出编译时异常(受查异常),用户必须处理,否则无法通过编译
    在这里插入图片描述
    如图所示,CloneNotSupportedException是一个编译时异常,所以在编写代码的时候必须处理这个异常(本例中采用throws声明异常)。

        public static void main(String[] args) throws CloneNotSupportedException {
            Person person = new Person();
            Person person1 = (Person) person.clone();
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ③异常一旦抛出,其后的代码不会被执行

        public static void main(String[] args) {
            func1(0);
        }
    
        public static void func1(int a) {
            if (a == 0) {
                throw new NullPointerException();
            }
            System.out.println("方法结束");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行结果:
    在这里插入图片描述
    分析:打印语句 System.out.println("方法结束");因为在它之前抛出了异常,所以这条语句没有被执行。

    1. 异常的具体处理方式,也就是异常的捕获,主要有2种方式:异常声明throws以及try-catch捕获处理。

    2. 异常声明throws
      ①throws可以理解为一种较为不负责的做法,它只是告诉调用者我这里有异常,声明了异常,但是并没有真正的去解决处理这个异常。即,当方法种抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。当我们没有解决这个异常的时候,就会把这个异常交给JVM处理,一旦交给JVM,程序就崩溃了。
      ②throws的位置是在方法声明参数列表之后
      ③throw声明的异常必须是Exception或者Exception子类。

        public static void main(String[] args) throws NullPointerException,ArithmeticException{
            func(null,0);
        }
    
        public static void func(int[] array, int a) throws NullPointerException,ArithmeticException{
            if (array == null) {
                throw new NullPointerException();
            }
            if (a == 0) {
                throw new ArithmeticException();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    分析:在func中可能会抛出NullPointerException,ArithmeticException两种异常,所以在func的参数列表后面紧跟着使用throws进行声明,并且方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,假如抛出的多个异常之间具有父子关系,直接声明父类就可以。

    1. 异常捕获try-catch
      ①throws对异常并没有真正处理,而是将异常报告给抛出异常方法的调用者,由调用者处理。如果真正要对异常进行处理,就需要try-catch。
        public static void main(String[] args) {
            try {
                func(0);
                System.out.println("我会不会被执行呢?");
            }catch (ArithmeticException e){
                System.out.println("捕获到异常了");
            }
            System.out.println("捕获异常处理后正常流程的语句");
        }
    
        public static void func(int a){
            if (a == 0) {
                throw new ArithmeticException();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    运行结果:
    在这里插入图片描述
    分析:try块内抛出异常位置之后的代码 System.out.println("我会不会被执行呢?");,不会被执行。

    ②问题:try-catch能不能处理编译时异常(受查异常)?
    答:可以的,对于受查异常来说,当try中没有当初catch的受查异常的时候,catch检测不到就会报错。因此,对于catch中如果写受查异常,则try中一定要出现受查异常。

        public static void main(String[] args) {
            try {
                func(0);
                System.out.println("我会不会被执行呢?");
            }catch (ArithmeticException e){
                System.out.println("捕获到异常了");
            }catch (CloneNotSupportedException e){
                System.out.println("受查异常被捕获");
            }
            System.out.println("捕获异常处理后正常流程的语句");
        }
    
        public static void func(int a) throws CloneNotSupportedException{
            if (a == 0) {
                throw new CloneNotSupportedException();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    运行结果:
    在这里插入图片描述
    分析:在func中会抛出受查异常CloneNotSupportedException,但是func并没有处理这个受查异常,所以在func方法参数列表之后使用throws声明这个异常,因此真正的处理时在main中的catch捕获这个异常的时候,这个时候才叫真正意义上的处理异常。

    ③如果try抛出异常类型和catch的异常类型不匹配,则异常也不会被成功捕获,也就不会被处理,则会继续往外抛,直到JVM处理,一旦交给JVM处理,程序就会立即中止。【异常是按照类型来进行捕获的】

        public static void main(String[] args) {
            try {
                int[] array = null;
                int a = array.length;
            }catch (ArithmeticException e) {
                System.out.println("算术异常被捕获");
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行结果:
    在这里插入图片描述
    分析:try当中抛出的是空指针异常,而catch捕获处理的是算术异常,因此最终异常会交给JVM处理,程序中止。

    ④问:try会不会在同一时间同时抛出2个及以上的异常?
    答:不会的,当try当中存在多个异常的时候,从上往下执行,谁先抛出异常就捕获哪个异常。

        public static void main(String[] args) {
            try {
                int[] array = null;
                int a = array.length;
                int ret = 10 / 0;
            }catch (ArithmeticException e) {
                System.out.println("算术异常被捕获");
            }catch (NullPointerException e){
                System.out.println("空指针异常被捕获");
            }
            System.out.println("正常流程语句");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行结果:
    在这里插入图片描述
    分析:在try中会存在抛出空指针异常和算术异常两种情况,所以必须用多个catch来捕获,即多种异常,多次捕获。但是由于try中先会抛出空指针异常,所以不会继续抛其后的算术异常,但是我们发现catch的算术异常是写在空指针异常前面的,这也说明catch中程序的书写不影响异常的捕获,而是取决于try中抛哪个异常。

    ⑤如果多个异常的处理方式是完全相同的,可以使用 | 连接。

    catch (ArithmeticException | NullPointerException e) {
                System.out.println("算术异常或者空指针异常被捕获");
            }
    
    • 1
    • 2
    • 3

    ⑥问:能不能把Exception异常写在最前面
    答:不可以,因为所有的异常都是继承Exception的,此时Exception作为所有异常的父类,它能捕获所有的异常,如果把它放到catch的第一个,那么后续的异常没有任何作用了。
    错误写法:Exception放在最前面
    在这里插入图片描述
    正确做法:把Exception放到最后面。
    在这里插入图片描述
    ⑦可以通过一个catch捕获所有的异常,catch Exception,因为Exception是所有异常类的父类,因此可以用这个一类型表示捕获所有异常。
    注意:catch进行类型匹配的时候,不光会匹配相同类型的异常对象,也会捕获目标异常类型的子类对象。

            try {
                int[] array = null;
                int a = array.length;
                int ret = 10 / 0;
            }catch (Exception e) {
                System.out.println("异常被捕获");
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    分析:这种做法不推荐,不可取。

    1. 有些特定的代码,不论程序是否发生异常,都需要执行,比如对资源的回收释放,而异常会引发程序的跳转,导致有些语句执行不到,finally就是用来解决这个问题的。
    try{
    //可能会发生异常的代码
    }catch(异常类型 e){
    //对捕获到的异常进行处理
    }finally{
    //此处的语句无论是否发生异常,都会被执行
    }
    //如果没有抛出异常,或者异常被捕获处理了,这里的代码也会被执行
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意:fnally中的代码一定会执行,一般在finnally中进行一些资源清理的扫尾工作。
    分析:下面程序输出结果:

    // 下面程序输出什么?
    public static void main(String[] args) {
    System.out.println(func());
    }
    public static int func() {
    try {
    return 10;
    } finally {
    return 20;
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    答:输出20,因为本来要返回10,但是finally中的return语句要在方法结束之前执行,所以在返回10之前,变成了返回20。
    分析:finally执行的时机是方法返回之前(try或者catch中如果由return,会在这个return之前执行finally),但是如果finally中存在return语句,那么就会执行finally中的return,从而不会执行到try中原有的return。【一般不建议在finally中写return语句,会被编译器当成一个警告。】并且finally中的语句一定会被执行。

    1. 异常处理流程总结
      ①程序先执行try中的代码
      ②如果try中的代码出现异常,就会结束try中的代码,看和catch中的异常类型是否匹配
      ③——Ⅰ:如果找到匹配的异常类型,就会执行catch中的代码
      ③——Ⅱ:如果没有找到匹配的异常类型,就会将异常向上传递到上层调用者
      ④无论是否找到匹配的异常类型,finally中的代码都会被执行到(在该方法结束之前执行)
      ⑤如果上层调用者也没有处理异常,就继续向上传递
      ⑥一直到main方法也没有合适的代码处理异常,就会交给JVM来进行处理,此时程序就会异常终止

    2. 自定义异常:为什么要自己取定义异常?
      Java中虽然已经内置了丰富的异常类,但是并不能完全表示实际开发中所遇到的一些异常,此时就需要维护符合实际情况的异常结构,基于已有的异常类进行扩展(继承)创建和业务相关的异常类。
      具体方式:
      ①自定义异常类,继承Exception(编译时异常)或者RuntimeException(运行时异常)。
      ②实现一个带有String类型参数的构造方法,参数含义:出现异常的原因。
      注意事项:
      ①自定义异常通常会继承Exception类或者RuntimeException类
      ②继承自Exception的异常默认是受查异常
      ③继承自RuntimeException的异常默认是非受查异常
      实现:实现一个简单的控制台版用户登陆程序, 程序启动提示用户输入用户名密码. 如果用户名密码出错, 使用自定义异常的方式来处理
      用户名异常类:

    public class NameExcepetion extends RuntimeException{
        public NameExcepetion(String message) {
            super(message);
        }
    
        public NameExcepetion() {
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    密码异常类:

    public class PasswordExcepetion extends RuntimeException{
        public PasswordExcepetion() {
        }
    
        public PasswordExcepetion(String message) {
            super(message);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    测试类:

    import java.util.Scanner;
    
    public class Test {
        //异常练习
        public static void login() {
            String name = "123";
            String password = "567";
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入用户名");
            String uname = scanner.nextLine();
            System.out.println("请输入密码");
            String upassword = scanner.nextLine();
    
            if (!name.equals(uname)) {
                throw new NameExcepetion("兄弟用户名错误了");
            }
    
            if (!password.equals(upassword)) {
                throw new PasswordExcepetion("兄弟密码错误了");
            }
    
        }
    
    
        public static void main(String[] args) {
            try {
                login();
            } catch (NameExcepetion e) {
                e.printStackTrace();
                System.out.println("用户名错误");
            } catch (PasswordExcepetion e) {
                e.printStackTrace();
                System.out.println("密码错误");
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    运行结果:
    在这里插入图片描述
    分析:自定义异常类是一个闪电的图标。
    在这里插入图片描述

  • 相关阅读:
    C++学习 --文件
    Android Gradle插件与Gradle的区别
    Android移动开发基础——实训项目:个人财务软件
    关于MuLoginWebGL介绍说明:
    关于Vue3的笔记
    docker_01_基础
    【Wifi认证 攻击】
    小程序生态入局,让智能电视更上一层楼?
    Google IO 2023推出Android Studio官方AI工具Studio Bot
    强者互帮,弱者互撕!
  • 原文地址:https://blog.csdn.net/weixin_44070116/article/details/128032435