java异常的使用(摆脱唯唯诺诺之“干了再说”)

异常

  • 首先是上期的String对象一共生成了11个(包括底层生成的存于哈希表中的那个哈~!)

  • 首先要知道异常有两大类,一个是编译期异常、一个是运行时异常

  • 编译器异常:如System.out.println写成system.out.println,此时编译时就会报错,或者说在你的IDEA里此代码当场报红

  • 运行时异常:当程序跑起来了,才在IDEA底下的结果窗口中弹出异常的信息,所以说此时代码是可以通过编译的


  • 使用异常的好处:可以让我们的异常都统一放在一起,方便管理,看着也舒服;要不然采用我们平时写代码的习惯,每个都用个if语句去判断,可能造成正常运行的代码和出现异常的代码都混在一起的现象。

异常的基本用法😋

try{
    有可能出现异常的语句都丢在这;
}catch(异常类型 异常对象){
    处理异常的语句;
}finally{
    异常的出口;//这个finally的部分可以不写,写了的话,这里面的语句是一定会被执行的,即使try语句里有return,finally里的语句都要先执行!
}
  • 代码执行到有异常的语句后,如果我们自己不去处理,其结果是什么?

    结果是这个异常会交给JVM去处理!而JVM处理的结果就是,代码从这个出现异常的语句开始往下的代码全部终止!或者说异常终止,或者说奔溃了。

  • 那给一个我们主动去处理一个异常的情况看看:

public class Test {
    public static void main(String[] args) {
        int n=0;
        try{
            if(n==0){
                throw new Exception("抛出异常咯!");//这里new对象的时候调用的是内置异常类,这句话一旦执行,这个异常会在结果栏里给出定位(异常代码所在行号,这也是异常使用的好处之一)
                // Exception的特定构造方法
            }
        }catch(Exception e){
            e.printStackTrace();//打印异常e,这里就相当于处理异常了
        }
        System.out.println("hehe");
    }
}
/*
结果:
java.lang.Exception: 抛出异常咯!
	at Test.main(Test.java:13)
hehe

Process finished with exit code 0
*/

上述代码给我们的启示是:即使有异常出现(被抛出),我们将其处理之后,不影响后续代码的执行,不至于整个程序都崩溃。而如果我们自己不处理,就交由JVM处理,其结果就是程序将终止与第六行代码。

  • 其次我们try{}内的语句从上往下依次执行时,一旦出现某个语句抛出异常,那这个{}内该代码以下的代码不会再执行!

关于异常打印的顺序🍊

  • 我们知道JVM处理异常后,将异常信息栈的异常都将打印出来,因为这是一个栈,所以说先进去的异常信息后出来,后进去的异常信息会先出来,就比如:
public class Test {
    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        int n=scanner.nextInt();
        System.out.println(n);
    }
}

当我们从键盘键入一些字母之后,查看最后的异常信息:

sasa
Exception in thread "main" java.util.InputMismatchException
	at java.util.Scanner.throwFor(Scanner.java:864)
	at java.util.Scanner.next(Scanner.java:1485)
	at java.util.Scanner.nextInt(Scanner.java:2117)
	at java.util.Scanner.nextInt(Scanner.java:2076)
	at Test.main(Test.java:13)

由上面提到的先进后出的异常信息可知,第一个出现的异常是main类的第13行,而上面的异常信息都是内置类的源码出错!(我们不可能怀疑源码),所以我们就乖乖去修改第13行代码就可以啦!改完(我们自己可以去用try-catch去包裹它,包裹:surround)即可。


处理多个异常的情况🍨

public class Test {
    public static void main(String[] args) {
        int[] arr={1,2,3};
        try{
            arr=null;
            System.out.println(arr[5]);
            System.out.println(arr.length);
        }catch (ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
            System.out.println("捕捉到了数组越界异常!");
        }catch (NullPointerException e){
            e.printStackTrace();
            System.out.println("捕捉到了空指针异常!");
        }
        System.out.println("嘻嘻!");
    }
}
/*
结果:
java.lang.NullPointerException
	at Test.main(Test.java:15)
捕捉到了空指针异常!
嘻嘻!
*/

还是上面提到的原则:try里第一个抛出异常的语句执行完,try内的之后的语句都终止执行。

当然我们可以多捕获多个异常的代码换一种写法:

public class Test {
    public static void main(String[] args) {
        int[] arr={1,2,3};
        try{
            arr=null;
            System.out.println(arr[5]);
            System.out.println(arr.length);
        }catch (ArrayIndexOutOfBoundsException | NullPointerException e){//异常想写几个写几个,e只能写一个哦
            e.printStackTrace();//根据异常信息判断是哪个异常即可
        }
        System.out.println("嘻嘻!");
    }
}

异常的体系结构🌊

在这里插入图片描述

  • 顶层类Throwable派生出两个而重要子类:Error和Exceptiom
  • 其中Error代表java运行时内部错误或者资源耗尽错误,应用程序不会抛出这个异常,这种内部错误一旦出现,除了告知用户并使程序终止之外,无能为力,这种情况很少出现
  • Exception是我们程序员所使用的异常类的父类
  • 其中Exception有一个子类叫RuntimeException,这里面又派生出许多我们常见的异常类,如:NullpointerException等

  • 说上面的是因为:我们用catch去捕捉异常类时是可以用父类异常去捕捉子类异常的,从逻辑上来说,我们可以用Exception去捕捉所有的它的子类异常!但不建议这样去做,因为Exception涵盖的异常类太多,可能把控不好。最好是写我们想捕捉的具体的异常类,尽量不要使用父类异常去捕捉子类异常,但没说不可用哈。

  • 若一段代码中既有父类异常去捕获一个异常,也有具体的异常去捕获异常,那书写顺序是:先写子类异常去捕获,然后再父类,写反了,当场报错。

  • 当我们不知道一个异常的父类到底是谁时:我们可以左键点击一下异常类,然后右键→Diagram→show就可以看见整个继承关系了!:(社区办可能没有这个功能,想不花钱用旗舰版,那只能破解版了)
    在这里插入图片描述

  • 其次很重要的一点:函数处理异常的时候,本函数抛出异常如果么有当场解决,这个异常就会自动抛给调用这个函数的函数,调用者(主函数)如果还不处理这个异常,那就只能交由JVM去处理这个异常了!换句话说就是:异常会沿着异常的信息调用栈进行传递!!!!


finally的使用🌴

  • finally表示最后的善后工作,例如释放资源
public class Test {
    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);//Scanner相当于一个资源!~
        try{
            int n=scanner.nextInt();
            System.out.println(10/n);
        }catch(InputMismatchException e){
            e.printStackTrace();
            System.out.println("捕捉到了输入不匹配异常!");
        }catch(ArithmeticException e){
            e.printStackTrace();
            System.out.println("捕捉到了算术异常,可能除数为0");
        }finally {
            scanner.close();
        }
    }
}

针对上述代码,这里有一个小技巧一步到位:

public class Test {
    public static void main(String[] args) {
        try(Scanner scanner=new Scanner(System.in)){//这里可以做到try里面的语句执行完毕,自动调用scanner.close()
            int n=scanner.nextInt();
            System.out.println(10/n);
        }catch(InputMismatchException e){
            e.printStackTrace();
            System.out.println("捕捉到了输入不匹配异常!");
        }catch(ArithmeticException e){
            e.printStackTrace();
            System.out.println("捕捉到了算术异常,可能除数为0");
        }
    }
}
  • 对上述代码:一个函数抛出异常(本函数内部不作处理!)我想把异常传给主函数,要对被调用的函数做异常申明:
public class Test {
    public static void func()throws Exception{//throws就用于声明异常啦,所以说throws用在声明,而throw用在抛出异常,给异常定位!
        //哪里写了throw,只要这个语句被执行,最后这行代码会被标出来,到时候我们直接过去改就可以了!
        throw new Exception();
    }
    public static void main(String[] args) {
        try{
            func();
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("捕捉到了被调用函数的异常啦!");
        }
        System.out.println("haha");
    }
}
  • 针对finally里面的语句,我们要知道,即使try{}内有return 的语句,我们finally里面的语句也会在此之前执行(所以说我们尽量不要在finally里面写return 的语句!),如:
public class Test {
    public static int func(){
        int a=10;
        try{
            return a;
        }catch(ArithmeticException e){
            e.printStackTrace();
        }finally{
            return 20;
        }
    }
    public static void main(String[] args) {
        int ret=func();
        System.out.println(ret);
    }
}//打印结果是什么
//20,因为finally里面的语句先执行(因为要在func方法结束之前执行),返回20后,try里面的语句没法返回了!

异常的处理流程❔

  1. 程序先执行try中的代码
  2. 如果try中的代码出现异常,就会结束try中的代码,看catch 中的异常是否匹配
  3. 如果找到匹配的异常,就会执行对应catch中的代码
  4. 如果没有找到匹配的catch的异常,就会传递给调用者
  5. 无论是否找到匹配的异常类型,finally里面的代码都会被执行,finally所在方法结束之前执行!
  6. 如果上层调用者没有处理异常,继续往上面传
  7. 一直穿到了main函数也没有人去处理这个异常,那这个异常将会交由JVM处理,此时程序就会出现异常终止,结果代码是code 1

手动抛出异常(throw)🍉

  • 除了java中内置的一些异常类之外,程序员也可以手动抛出某个异常,使用throw关键字来完成这个操作(前述已提及throws做异常声明)。
public class Test{
    public static void func(int x){//最好能在这做一个申明
        if(x==0){
            throw new ArithmeticException("抛出一个异常,你可以在这里描述这个异常")
        }
    }
    public static void main(String[] args){
        func(0);
    }
}//这里只是抛出异常,我并没有去处理它
//结果
Exception in thread "main" java.lang.ArithmeticException: 抛出一个异常,你可以在这里描述这个异常
	at Test.func(Test.java:14)
	at Test.main(Test.java:18)
    Process finished with exit code 1   //这个1就是因为是JVM处理了这个异常,如果是我们自己处理的就是数字0

受查异常必须显式去处理是什么意思?🙋‍♂

  • 受查异常前面也有所提及,它是指Exception子类中除了RuntimeException之外的类,这些类一旦出现,我们在程序运行之前就可以看到代码报红了,这里意思就是说,你不处理这个异常,你连编译都别想过去。
  • 而显式处理这种异常的方法主要有两种,一个是用try-catch进行包裹;一个就是“往上抛”,所谓“往上抛”就是说,当前方法只管用这个类,出不出错我都不管,我就把这个可能的异常抛给调用者,调用者如果想处理,就去catch它,或者调用者也不想处理,调用者也将这个可能的异常往上抛,就连我们的main函数都可以这样做,当main函数也将这个可能的异常往上抛时,那就相当于交给JVM处理了。因为中间没有一个人想处理这个“烂摊子”。

举个例子:比如我们常用的接口Clonable(注意事项已写在代码里):

class Person implements Cloneable{
    public int age=10;

    @Override
    protected Object clone() throws CloneNotSupportedException {//下面的clone()属于受查异常,super可能是不允许克隆的
        //这里就相当于这事我先干了,异常交给上头去处理。
        return super.clone();
    }
}
public class Test1 {
    public static void main(String[] args) throws CloneNotSupportedException {//这里也是把clone()的可能异常往上抛,如果出现异常
        //这里就相当于交给JVM去处理了。
        Person person=new Person();
        Person person1=(Person)person.clone();
        System.out.println(person1.age);
    }
}

用try-catch进行包裹,不再赘述,就是前面的例子,就是我们当场就把那个可能的异常给处理了的意思。


什么是自定义异常?怎么使用?✈️

  • java中虽然已经内置了丰富的异常类,但是实际场景中我们可能还需要对异常类进行拓展,创建符合我们自己的异常类。
  • 那使用自定义异常类是必须继承一个内置的异常类的,内置的异常类无非就是两种:一种是受查异常(Exception)、一种就是非受查异常(RuntimeException)。

举例(注意事项已写在代码内部)

class MyException1 extends Exception{
    public MyException1(String message) {
        super(message);
    }
}//自定义受查异常1
class MyException2 extends RuntimeException{
    public MyException2(String message) {
        super(message);
    }
}//自定义受查异常2
public class Test2 {
    public static void func1(int x){
        try{//使用try-catch包裹显式处理这个受查异常
            if(x==0){
                throw new MyException1("hehe");//此处子类构造要先帮父类进行构造
            }
        }catch (MyException1 e){
            e.printStackTrace();
        }
    }
    public static void func2(int x)throws MyException2{//“往上抛”
        if(x==0){
            throw new MyException2("HAHA");//这个也是子类构造前要先帮父类进行构造
        }
    }
    public static void main(String[] args) {
        func1(0);
        func2(0);
    }
}
//结果:
MyException1: hehe
	at Test2.func1(Test2.java:22)
	at Test2.main(Test2.java:34)
Exception in thread "main" MyException2: HAHA
	at Test2.func2(Test2.java:30)
	at Test2.main(Test2.java:35)

Process finished with exit code 1//这里是1是因为,func2()往上抛异常,但我main并没有去catch这个可能的异常,所以交由JVM去处理,那就会出现这样的异常终止代码1
  • 自定义异常的使用:
class NameException1 extends Exception{
    public NameException1(String message) {
        super(message);
    }
}
class PasswordException2 extends Exception{
    public PasswordException2(String message) {
        super(message);
    }
}
public class Test3 {
    private static final String name="hehe";//static final修饰的成员变量必须初始化
    private static final String psaaword="123";//同理

    public static void login(String name,String password)throws NameException1,PasswordException2{
        if(!Test3.name.equals(name)){
            throw new NameException1("用户名错误!");//仅抛出异常不行,要显式处理
        }
        if(!Test3.psaaword.equals(password)){
            throw new PasswordException2("密码错误!");
        }
    }

    public static void main(String[] args) {
        try{//try-catch包裹进行处理显式异常
            login("hehe","1234");
        }catch (NameException1 e){
            e.printStackTrace();//定位异常
            System.out.println("用户名错误!");
        }catch(PasswordException2 e){
            e.printStackTrace();//定位异常
            System.out.println("密码错误!");
        }
    }
}
//结果:
PasswordException2: 密码错误!
	at Test3.login(Test3.java:27)
	at Test3.main(Test3.java:33)
密码错误!

Process finished with exit code 0//因为异常是我们自己处理的,所以code 0

一个练习题:使用while循环建立类似“恢复模型”的异常处理行为,他将不断重复,直到异常不再抛出!下期揭晓答案(答案不唯一,起到类似的效果即可)🍂

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 26
    评论
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值