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(捕获操作可能产生的异常类型 变量){
//处理异常
}
...

优势:正常流程和错误流程分开,程序员更关注正常流程,代码也更清晰,异常处理的核心思想就是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();
        }
    }
}

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

    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person();
        Person person1 = (Person) person.clone();

    }

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

    public static void main(String[] args) {
        func1(0);
    }

    public static void func1(int a) {
        if (a == 0) {
            throw new NullPointerException();
        }
        System.out.println("方法结束");
    }

运行结果:
在这里插入图片描述
分析:打印语句 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();
        }
    }

分析:在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();
        }
    }

运行结果:
在这里插入图片描述
分析: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();
        }
    }

运行结果:
在这里插入图片描述
分析:在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("算术异常被捕获");
        }
    }

运行结果:
在这里插入图片描述
分析: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("正常流程语句");
    }

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

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

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

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

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

// 下面程序输出什么?
public static void main(String[] args) {
System.out.println(func());
}
public static int func() {
try {
return 10;
} finally {
return 20;
}
}

答:输出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() {
    }
}

密码异常类:

public class PasswordExcepetion extends RuntimeException{
    public PasswordExcepetion() {
    }

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

测试类:

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("密码错误");
        }
    }
}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeKnightShuai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值