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

本文详细介绍了Java的异常处理机制,包括编译时异常和运行时异常的处理方式。强调了try-catch-finally、throws关键字的使用,以及自定义异常类的重要性。通过实例展示了如何通过异常处理确保代码的稳健性,特别是在处理用户输入和业务逻辑时避免程序崩溃。同时,提出了在实际开发中,推荐使用异常处理方式三是最佳实践,即在底层抛出异常,由最外层集中捕获处理。
摘要由CSDN通过智能技术生成

一、异常处理机制

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

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

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

(1-1)抛出异常格式
方法 throws 异常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虚拟机才会检测到这里的异常!
    }
}
  • 这里可以看到出现了一个编译时异常了:

    在这里插入图片描述



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

    在这里插入图片描述



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

    在这里插入图片描述



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

    在这里插入图片描述



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

    在这里插入图片描述



  • 注意:

    在这里插入图片描述


    在这里插入图片描述



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

(1-2)规范做法
方法 throws Exception {
    
}
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-3)总结:这种方式不好!


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

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

(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("找不到指定文件!!");
        }
    }
}
程序开始...
出现了解析时间异常!!
找不到指定文件!!
程序结束.

Process finished with exit code 0

  • 可以看到,使用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("找不到指定文件!!");
        }
    }
}

程序开始...
出现了解析时间异常!!
程序结束.

Process finished with exit code 0

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

(2-4)建议格式(企业级写法)
  • Exception可以捕获处理一切异常类型
try{
    // 可能出现异常的代码!
}catch(Exception e) {
    e.printStackTrace();	// 直接打印异常栈信息
}
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方法,打印异常栈信息(规范)
        }
    }
}

在这里插入图片描述




(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、异常处理的总结

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


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、案例一

需求

  • 键盘录入一个合理的价格为止(必须是数值,值必须大于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!输入的价格必须是正数哦~~");
            }
        }
    }
}

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

在这里插入图片描述


  • 但是,有个问题,假如用户这样子输入呢?
  • 这样子,就会出现数据类型转换异常: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("您输入的价格不合理,请重新输入一个合理的价格!");
            }
        }
    }
}

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

在这里插入图片描述




三、自定义异常

  • 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 + "岁,年龄合法!");
        }
    }
}

在这里插入图片描述




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

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述




总结

1、自定义编译时异常

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

2、自定义运行时异常

  • 定义一个异常类继承RuntimeException
  • 重写构造器
  • 在出现异常的地方用throw new自定义对象抛出
  • 作用:提醒不强烈,编译阶段不会报错!运行时才可能会报错!
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值