• 83-Java异常:处理机制、案例、自定义异常


    一、异常处理机制

    1、编译时异常的处理机制
    • 编译时异常是编译阶段就会报错!所以必须进行处理,否则代码无法通过!

    2、编译时异常的三种处理形式
    • 出现异常直接抛出给调用者,调用者继续抛出去。
    • 出现异常自己捕获处理,不麻烦别人。
    • 前两者结合,出现异常直接抛出给调用者,调用者捕获处理。

    (1)异常处理方式一
    • 关键字:throws,用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理。
    • 这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给JVM虚拟机将引起程序终止

    (1-1)抛出异常格式
    方法 throws 异常1, 异常2, 异常3, ...{
        
    }
    
    • 1
    • 2
    • 3
    package com.app.d8_exception_handle;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.InputStream;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
        目标:掌握编译时异常处理方式一,并清楚其缺陷
        方式一:
            在出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给JVM虚拟机。
            JVM虚拟机输出异常信息,直接终止程序,这种方式与默认方式是一样的。
            虽然可以解决代码编译时的报错!但是一旦运行时真的出现异常,就会终止程序,项目也无法正常运行!
            因此,这种方式并不好!!
     */
    public class ExceptionDemo1 {
        public static void main(String[] args) throws ParseException, FileNotFoundException {
            System.out.println("程序开始...");
            parseTime("2022-09-07 14:33:22");
            System.out.println("程序结束.");
        }
    
        public static void parseTime(String date) throws ParseException, FileNotFoundException {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM-dd HH:mm:ss");  // 运行时会报错!
    //        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date d = sdf.parse(date);
            System.out.println(d);
    
            // 不需要理解这行代码的意义
            InputStream is = new FileInputStream("E:/ab.jpg");    // 当上一步代码没有bug时,JVM虚拟机才会检测到这里的异常!
        }
    }
    
    • 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
    • 这里可以看到出现了一个编译时异常了:

      在这里插入图片描述



    • 直接使用throws关键字,将异常抛出给调用者:

      在这里插入图片描述



    • 调用者也直接使用throws关键字,将异常抛出给JVM虚拟机:

      在这里插入图片描述



    • 在编译阶段已经看不到出错了!将异常抛出给JVM虚拟机时,当程序没有bug,就可以正常运行:

      在这里插入图片描述



    • 将异常抛出给JVM虚拟机时,当程序有bug,JVM虚拟机就会输出异常信息,程序终止:

      在这里插入图片描述



    • 注意:

      在这里插入图片描述


      在这里插入图片描述



    • 小结:无论你使用throws抛出多少个异常,其实真正抛出的只有一个异常,只有处理完前一个的bug,JVM虚拟机才会检测到后一个的bug

    (1-2)规范做法
    方法 throws Exception {
        
    }
    
    • 1
    • 2
    • 3
    package com.app.d8_exception_handle;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.InputStream;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
        目标:掌握编译时异常处理方式一,并清楚其缺陷
        方式一:
            在出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给JVM虚拟机。
            JVM虚拟机输出异常信息,直接终止程序,这种方式与默认方式是一样的。
            虽然可以解决代码编译时的报错!但是一旦运行时真的出现异常,就会终止程序,项目也无法正常运行!
            因此,这种方式并不好!!
     */
    public class ExceptionDemo1 {
        public static void main(String[] args) throws Exception {
            System.out.println("程序开始...");
            parseTime("2022-09-07 14:33:22");
            System.out.println("程序结束.");
        }
    
        public static void parseTime(String date) throws Exception {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM-dd HH:mm:ss");  // 运行时会报错!
    //        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date d = sdf.parse(date);
            System.out.println(d);
    
            // 不需要理解这行代码的意义
            InputStream is = new FileInputStream("E:/ab.jpg");    // 当上一步代码没有bug时,JVM虚拟机才会检测到这里的异常!
        }
    }
    
    • 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

    (1-3)总结:这种方式不好!


    (2)异常处理方式二
    • try...catch...
      • 监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理。
      • 这种方式还可以,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行

    (2-1)格式
    try{
        // 监视可能出现异常的代码!
    }catch(异常类型1 变量) {
        // 处理异常
    }catch(异常类型2 变量) {
        // 处理异常
    }...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2-2)单独处理(不推荐)
    package com.app.d8_exception_handle;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.InputStream;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
        目标:掌握编译时异常处理方式二,并清楚其作用
        方式二:
            在编译阶段,监视捕获异常,发生异常的方法自己独立完成异常的处理,
            这样程序就可以继续往下执行,不会终止!
     */
    public class ExceptionDemo2 {
        public static void main(String[] args) {
            System.out.println("程序开始...");
            parseTime("2022-09-07 14:33:22");
            System.out.println("程序结束.");
        }
    
        public static void parseTime(String date) {
            try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM-dd HH:mm:ss");  // 运行时会报错!
                Date d = sdf.parse(date);
                System.out.println(d);
            } catch (ParseException e) {
                // 解析出现的问题
                System.out.println("出现了解析时间异常!!");
            }
    
            try {
                // 不需要理解这行代码的意义
                InputStream is = new FileInputStream("E:/ab.jpg");
            } catch (FileNotFoundException e) {
                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
    • 38
    • 39
    • 40
    程序开始...
    出现了解析时间异常!!
    找不到指定文件!!
    程序结束.
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    • 可以看到,使用try...catch...监视捕获异常后,程序可以正常的运行完成!!即使存在异常,也不会让程序立即终止!!

    (2-3)整体处理(推荐)
    package com.app.d8_exception_handle;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.InputStream;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
        目标:掌握编译时异常处理方式二,并清楚其作用
        方式二:
            在编译阶段,监视捕获异常,发生异常的方法自己独立完成异常的处理,
            这样程序就可以继续往下执行,不会终止!
     */
    public class ExceptionDemo2 {
        public static void main(String[] args) {
            System.out.println("程序开始...");
            parseTime("2022-09-07 14:33:22");
            System.out.println("程序结束.");
        }
    
        public static void parseTime(String date) {
            try {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM-dd HH:mm:ss");  // 运行时会报错!
                Date d = sdf.parse(date);
                System.out.println(d);
    
                // 不需要理解这行代码的意义
                InputStream is = new FileInputStream("E:/ab.jpg");	// 当前面的代码没有bug时,才会检测到此处的bug
            } catch (ParseException e) {
                // 解析出现的问题
                System.out.println("出现了解析时间异常!!");
            } catch (FileNotFoundException e) {
                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
    • 38
    • 39
    程序开始...
    出现了解析时间异常!!
    程序结束.
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5

    • 可以看到,方法内部的前面的代码出现了异常,因此后面的代码不会执行,但是不会终止程序的运行!!
    • 既然已经出现了异常,那就先解决前面的异常,再去捕获后面的异常,再进行解决!!

    (2-4)建议格式(企业级写法)
    • Exception可以捕获处理一切异常类型
    try{
        // 可能出现异常的代码!
    }catch(Exception e) {
        e.printStackTrace();	// 直接打印异常栈信息
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    package com.app.d8_exception_handle;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.InputStream;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
        目标:掌握编译时异常处理方式二,并清楚其作用
        方式二:
            在编译阶段,监视捕获异常,发生异常的方法自己独立完成异常的处理,
            这样程序就可以继续往下执行,不会终止!
     */
    public class ExceptionDemo2 {
        public static void main(String[] args) {
            System.out.println("程序开始...");
            parseTime("2022-09-07 14:33:22");
            System.out.println("程序结束.");
        }
    
        public static void parseTime(String date) {
            try {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM-dd HH:mm:ss");  // 运行时会报错!
                Date d = sdf.parse(date);
                System.out.println(d);
    
                // 不需要理解这行代码的意义
                InputStream is = new FileInputStream("E:/ab.jpg");  // 当前面的代码没有bug时,才会捕获到此处的bug
            } catch (Exception e) {
                // 解析出现的异常
                e.printStackTrace();    // 调用printStackTrace方法,打印异常栈信息(规范)
            }
        }
    }
    
    • 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

    在这里插入图片描述




    (2-5)异常处理方式三
    • 前两种处理方式的结合体
      • 方法直接将异常通过throws抛出去给调用者;
      • 调用者收到异常后直接捕获处理。
    • 出现第三种方式的原因
      • 举个例子:
        • 我请小王帮我去买一支笔,小王很牛的说:“我干嘛帮你买?”,随后小王让小白去帮我买,小白去了。
        • 结果小白到的时候,店已经打烊了,此时小白发现了异常,然后小白自己独立处理掉了这个异常!
        • 之后,小王就懵了,啥情况都不知道!
        • 最后,我也懵了!!不知道笔买了还是没买!
      • 如果异常都在底层独立处理完了,那我根本不知道底层到底是成功了还是失败了!!
    package com.app.d8_exception_handle;
    
    import java.io.FileInputStream;
    import java.io.InputStream;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
        目标:掌握编译时异常处理方式三,并清楚其作用
        方式三:
            在编译阶段,将异常抛出去给调用者,调用者再使用try...catch...捕获处理异常,
            这样的好处是调用者既能了解到底层的执行情况,又能让程序继续往下执行,不会终止!
     */
    public class ExceptionDemo3 {
        public static void main(String[] args) {
            System.out.println("程序开始...");
            try {
                parseTime("2022-09-07 14:33:22");
                System.out.println("功能操作成功~");
            } catch (Exception e) {
                e.printStackTrace();    // 调用printStackTrace方法,打印异常栈信息(规范)
                System.out.println("功能操作失败!");
            }
            System.out.println("程序结束.");
        }
    
        public static void parseTime(String date) throws Exception {
    //        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM-dd HH:mm:ss");  // 运行时会报错!
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date d = sdf.parse(date);
            System.out.println(d);
    
            // 不需要理解这行代码的意义
            InputStream is = new FileInputStream("E:/ab.jpg");  // 当前面的代码没有bug时,才会捕获到此处的bug
        }
    }
    
    • 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

    在这里插入图片描述


    总结

    1、异常处理的总结

    • 开发中按照规范来说第三种方式是最好的:底层的异常抛出去给最外层,最外层集中捕获处理。好处就是外层可以知道底层是否执行成功
    • 实际应用中,只要代码能通过编译阶段,并且功能可以完成 ,那么每一种异常处理方式似乎也都是可以的


    3、运行时异常的处理机制
    • 运行时异常编译阶段不会报错,是运行时才可能报错的,所以编译阶段不处理也可以。
    • 按照规范建议还是处理:建议在最外层调用处集中捕获处理即可。
    package com.app.d9_exception_handle_runtime;
    
    /**
        目标:运行时异常的处理机制
        可以不处理,编译阶段代码也通过了
        按照理论规则:建议还是处理,只需要在外层捕获处理异常即可!
     */
    public class ExceptionDemo {
        public static void main(String[] args) {
            System.out.println("程序开始...");
            try {
                chu(10, 0);
                System.out.println("操作成功!");
            } catch (Exception e) {
                System.out.println("出现异常!");
                e.printStackTrace();    // 打印异常栈信息
            }
            System.out.println("程序结束.");
        }
    
        public static void chu(int a, int b) {
            System.out.println(a);
            System.out.println(b);
            int c = a / b;
            System.out.println(c);
        }
    }
    
    • 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

    在这里插入图片描述




    二、异常处理使代码更稳健的案例

    1、案例一

    需求

    • 键盘录入一个合理的价格为止(必须是数值,值必须大于0)。

    分析

    • 定义一个死循环,让用户不断的输入价格。

    未处理异常前

    package com.app.d10_exception_handle_test;
    
    import java.util.Scanner;
    
    /**
        目标:通过案例清楚并理解异常在实际开发中的作用
     */
    public class Test1 {
        public static void main(String[] args) {
            // 需求:键盘录入一个合理的价格为止(必须是数值,值必须大于0)
    
            // a、创建键盘录入对象,用于用户录入价格
            Scanner sc = new Scanner(System.in);
    
            // b、定义死循环,让用户不断输入价格,直到输入的价格合理为止
            while (true) {
                System.out.println("请您输入一个价格:");
                // c、定义一个字符串变量用于存放用户输入的价格
                /*
                    为啥用String接收,而不是用double接收??
                    因为用户难免会输错!!比如:23.2jjghj 等等
                 */
                String priceStr = sc.nextLine();
                // d、将接收到的String类型的价格转换成double类型的价格
                double price = Double.valueOf(priceStr);
    
                // e、判断用户输入的价格是否大于0
                if (price > 0) {
                    // 是,说明价格是正数
                    System.out.println("定价:" + price);
                    break;
                }else {
                    // 否,说明价格不是正数
                    System.out.println("sorry!输入的价格必须是正数哦~~");
                }
            }
        }
    }
    
    • 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
    • 38

    • 可以看到,用户如果输入负数,判断正常;用户输入合理价格,也正常!

    在这里插入图片描述


    • 但是,有个问题,假如用户这样子输入呢?
    • 这样子,就会出现数据类型转换异常:NumberFormatException,如果不处理异常,程序就会死亡、终止!

    在这里插入图片描述




    处理异常后

    package com.app.d10_exception_handle_test;
    
    import java.util.Scanner;
    
    /**
        目标:通过案例清楚并理解异常在实际开发中的作用
     */
    public class Test1 {
        public static void main(String[] args) {
            // 需求:键盘录入一个合理的价格为止(必须是数值,值必须大于0)
    
            // a、创建键盘录入对象,用于用户录入价格
            Scanner sc = new Scanner(System.in);
    
            // b、定义死循环,让用户不断输入价格,直到输入的价格合理为止
            while (true) {
                try {
                    System.out.println("请您输入一个价格:");
                    // c、定义一个字符串变量用于存放用户输入的价格
                /*
                    为啥用String接收,而不是用double接收??
                    因为用户难免会输错!!比如:23.2jjghj 等等
                 */
                    String priceStr = sc.nextLine();
                    // d、将接收到的String类型的价格转换成double类型的价格
                    double price = Double.valueOf(priceStr);
    
                    // e、判断用户输入的价格是否大于0
                    if (price > 0) {
                        // 是,说明价格是正数
                        System.out.println("定价:" + price);
                        break;
                    }else {
                        // 否,说明价格不是正数
                        System.out.println("sorry!输入的价格必须是正数哦~~");
                    }
                } catch (Exception e) {
                    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
    • 38
    • 39
    • 40
    • 41
    • 42

    • 可以看到,处理异常后,当出现异常时,就可以捕获处理掉,就不会让程序死亡、终止!!

    在这里插入图片描述




    三、自定义异常

    • Java无法为这个世界上全部的问题提供异常类。
    • 如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了。

    1、好处
    • 可以使用异常的机制管理业务问题,如提醒程序员注意。
    • 同时一旦出现bug,可以用异常的形式清晰的指出出错的地方。

    2、分类
    (1)自定义编译时异常
    • 定义一个异常类继承Exception。
    • 重写构造器。
    • 在出现异常的地方用throws new自定义对象抛出。
    • 作用:编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!

    在这里插入图片描述


    在这里插入图片描述


    package com.app.d11_exception_custom;
    
    /**
        目标:学会自定义异常类(了解)
        自定义异常类:
            1、自定义编译时异常
            2、自定义运行时异常
    
        需求:年龄小于0岁 或 大于200岁,就为异常
     */
    public class ExceptionDemo {
        public static void main(String[] args) {
            // 模拟调用者使用checkAge方法
            try {
                checkAge(13);
                checkAge(-23);
            } catch (AgeIsIllegalException e) {
                e.printStackTrace();
            }
        }
    
        public static void checkAge(int age) throws AgeIsIllegalException {
            if (age < 0 || age > 200) {
                // 抛出去一个异常给对象给调用者
                // throw:在方法内部直接创建一个异常对象,并从此点抛出
                // throws:用在方法申明上的,抛出方法内部的异常
                throw new AgeIsIllegalException(age + " is illegal!");
            }else {
                System.out.println(age + "岁,年龄合法!");
            }
        }
    }
    
    • 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

    在这里插入图片描述




    (2)自定义运行时异常
    • 定义一个异常类继承RuntimeException。
    • 重写构造器。
    • 在出现异常的地方用throws new自定义对象抛出。
    • 作用:运行时异常是编译阶段不报错,提醒不强烈,运行时才可能出现错误!

    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述




    总结

    1、自定义编译时异常

    • 定义一个异常类继承Exception
    • 重写构造器
    • 在出现异常的地方用throw new自定义对象抛出
    • 作用:编译时异常是编译阶段就报错!提醒更加强烈,一定需要处理异常!!

    2、自定义运行时异常

    • 定义一个异常类继承RuntimeException
    • 重写构造器
    • 在出现异常的地方用throw new自定义对象抛出
    • 作用:提醒不强烈,编译阶段不会报错!运行时才可能会报错!
  • 相关阅读:
    自动驾驶中的感知模型:实现安全与智能驾驶的关键
    【GO语言基础】前言
    [附源码]计算机毕业设计JAVAjsp在线开放课程平台
    Svelte生命周期(加整体概述)
    django框架管理员登录页面添加验证码功能
    [Android开发学iOS系列] 工具篇: Xcode使用和快捷键
    智能晾衣架(二)--功能实现
    Spring Boot项目开发实战:手动编写一个starter实现日志埋点记录信息
    Vivado与Notepad++关联步骤
    足球比赛系统的设计
  • 原文地址:https://blog.csdn.net/yelitoudu/article/details/126759010