拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 java例外的使用(摆脱唯唯诺诺之“干了再说”)

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

白鹭 - 2022-02-25 2115 0 0

例外

  • 首先是上期的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回圈建立类似“恢复模型”的例外处理行为,他将不断重复,直到例外不再抛出!下期揭晓答案(答案不唯一,起到类似的效果即可)

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *