【JavaSE】(9) 异常

一、什么是异常

        异常就是程序运行中的不正常行为(而非编译过程中,所以能通过编译),它不同于 bug (是不允许出现的问题),它是为了增加代码的鲁棒性,比如处理用户的网络不通畅、输入数据格式不对、内存不够等问题(是允许出现的问题)。

        出现异常的一些示例:

        注意,100 / 0.0 不是算术异常,在高等数学中等于无穷大:

二、异常的体系结构

        除了上面示例中的算术异常、数组越界异常、空指针异常,还有许多其它类别的异常,由 Java 标准库提供,且形成了一个完整的异常的体系结构。而其它语言,例如 C++,虽然也有异常的概念,但属于半成品,不好用,所以不推荐使用。Java 开发当中,异常是必不可少的。

        异常的体系结构分为两大部分:Error,由 JVM 内部使用;Exception,由程序员使用,就是我们通常说的异常。

三、异常的分类

        Exception 分为两类:受查异常非受查异常。写代码时,受查异常必须显示处理,否则编译不通过;非受查异常则不需要显示处理。

        打一个比方:感冒、蚊虫叮咬、黑眼圈之类的小毛病,可以自愈,且无大碍,就无需就医(受查异常);急性胃炎、食物中毒之类的大问题,就必须就医或者动手术(非受查异常)。

        受查异常示例

        未处理异常,编译不通过。按住 alt+shift+enter,自动添加抛出异常:

        RunTimeException 类及其子类异常,都属于非受查异常,即(一)中的示例。

        受查异常也叫编译时异常,非受查异常也叫运行时异常,但个人不喜欢这种叫法。异常都是在程序运行时发生的,这种叫法很容易引起误会。

四、throw 和 throws

        throw 表示抛出一个异常对象,是真的抛出;而 throws 表示声明该方法抛出了一些异常对象,属于画饼,实际抛没抛出是不知道的,目的是让调用者知道调用该方法需要处理异常

1、throw

  • 必须写在方法体内部
  • 抛出的必须是 Exception 或 Exception 子类的对象。
  • 若抛出的是非受查异常对象,则不用处理,交给 JVM 处理。
  • 若抛出的是受查异常对象,则必须处理(声明抛出异常对象),否则编译不通过。
  • 异常一旦抛出其后的代码就不执行

2、throws

  • 必须写在方法参数列表后
  • 声明的必须是 Exception 或 Exception 子类。
  • 一个方法同时抛出多个异常时,用逗号隔开。若这多个异常存在父子关系声明父类异常即可。

        但是,也不能通通直接声明抛出所有异常类的父类 Exception,因为这样仅仅知道可能会出现异常,但不知道异常的准确类型,难以针对性处理。就好像知道对象生气了,但是对象不告诉你生气的原因。

五、异常的处理

        异常是客观存在的,当出现异常,需要进行处理,处理的方式有两种:LBYL 和 EAFP。

1、事前防御型(LBYL)

       "Look Before Your Leap.",在操作之前就做检查。即先问后做。

        执行一个操作前,先检查其异常并处理。

boolean ret = false;
 ret = 登陆游戏();
 if (!ret) {
    处理登陆游戏错误;
    return;
 }
 ret = 开始匹配();
 if (!ret) {
    处理匹配错误;
    return;
 }
 ret = 游戏确认();
 if (!ret) {
    处理游戏确认错误;
    return;
 }
 ret = 选择英雄();
 if (!ret) {
    处理选择英雄错误;
    return;

        缺点:正常和异常流程混在一起,代码不易理解。

        这种处理方式,在 C、C++ 等这种不支持异常或者处理异常能力比较弱的编程语言中使用。

2、事后认错型(EAFP)

        "It's Easier to Ask Forgiveness than Permission.",事后获取原谅比事前得到许可更简单。即先做后问。

        先执行操作,如果操作抛出异常,再到对应的 catch 进行处理。

 try {
    登陆游戏();
    开始匹配();
    游戏确认();
    选择英雄();
    ...
 } catch (登陆游戏异常) {
    处理登陆游戏异常;
 } catch (开始匹配异常) {
    处理开始匹配异常;
 } catch (游戏确认异常) {
    处理游戏确认异常;
 } catch (选择英雄异常) {
    处理选择英雄异常;
 }
    ...

        优势:正常和异常流程分开,我们更关注的正常流程逻辑清晰,代码更易理解。

        这种处理方式,在 Java 这种处理异常能力强的编程语言中使用。

六、异常的捕获和处理 try-catch

        调用方法时,使用 try-catch 捕获和处理该方法声明抛出的异常。

1、try 内抛出异常后,后续代码的执行

        若抛出异常处,被 catch 捕获,则 try 内后续代码不会执行try-catch 外后续代码会执行

        printStackTrace 可以打印调用栈(层层抛出异常的位置)。

        若抛出异常处,没有匹配的 catch,即没被捕获,则会向上一级调用者抛出,到最后 main 方法的调用者是 JVM,若 main 没捕获异常,则最后被 JVM 处理,造成程序崩溃,退出码为非 0。换句话说,根据前面的结论,一旦抛出异常,后面的程序都不会执行

2、catch 多个异常

        当多个异常的处理方式相同,可以写成:

        父类异常可以捕获子类异常

        当多个异常,存在父子关系时,必须子类在前,父类在后(如果父类在子类前,父类把所所有子类及父类本身都捕获了,后面再跟个捕获子类就没意义了):

    七、finally

            我们知道,遇到 return 或者异常抛出,后续的代码都不会执行。当我们想 return 或者异常抛出后,立马释放资源怎么办?可以使用 finally(在当前方法结束前执行):

            实际的执行顺序:try 内抛出异常 >> finally 内释放资源 >> 层层抛出程序崩溃。但打印的结果是 “释放资源” 在最后,这里面有原因,println 和 printStackTrace 打印方式不同,具体原因可能以后会学到。

            如果 try、catch、finally 块都有 return,则 finally 会覆盖掉前面的

    八、自定义异常类

            异常处理,是根据业务来写的。比如电商平台,可能存在商品库存不足、用户余额不足等异常等。此时 Java 标准库提供的异常类可能不够我们使用,我们就需要根据业务自定义异常类

            自定义异常类继承 Exception 类或者 RuntimeException 类。继承  Exception 类默认为受查异常继承 RuntimeException 类默认为非受查异常

            示例,实现账号登陆异常处理:

    // 用户名异常类
    class UserNameException extends Exception {
        public UserNameException(String message) {
            super(message);
        }
    }
    
    // 密码异常类
    class PasswordException extends Exception {
        public PasswordException(String message) {
            super(message);
        }
    }
    
    // 登录
    class Login {
        public void login(String username, String password) throws UserNameException, PasswordException {
            if (!username.equals("admin"))
                throw new UserNameException("Invalid username"); // 字符串构造方法,出现异常的原因
            if (!password.equals("123"))
                throw new PasswordException("Invalid password");
            System.out.println("Login successful");
        }
    }
    
    public class Test2 {
        public static void main(String[] args) {
            Login login = new Login();
            try {
                login.login("张三", "123456");
            } catch (UserNameException | PasswordException e) { // 捕获异常
                System.out.println(e.getMessage());
            }
        }
    }
    

     

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值