异常

例子见Github-JavaSE-Day04

异常的概念

程序运行过程中出现的不正常情况
注意

  1. 相同代码在运行的时候,根据输入参数或者操作不同,可能会也可能不会发生异常,应该在写代码过程中尽量保证代码的正确性,不要到处是bug。
  2. 如果要解决代码中的异常,需要添加非常复杂的代码逻辑判断,代码会臃肿且要花大精力处理bug还不一定能解决。因此推荐使用异常机制来处理。
  3. 程序在运行过程中如果出现问题,会导致后面的代码无法正常执行,而使用异常机制后,可以对异常进行处理,同时后续的代码会继续执行,不会中断整个程序。
  4. 在异常处理过程中,不要只是简单地输出错误,要尽可能将详细异常进行输出
    void printStackTrace():打印异常的堆栈信息,可以从异常信息的最后一行开始追踪,寻找自己编写的Java类
    String printMessage():返回异常信息描述字符串,是printStackTrace()输出信息的一部分
    printStackTrace()更常用,因为更详细,会提供错误提示。

调用方法输出异常信息

在这里插入图片描述

异常处理

使用异常机制为程序提供了错误处理的能力。

  1. 预先设置好对付异常的处理方法
  2. 程序运行
  3. 遇到异常
  4. 对异常进行处理
  5. 处理完毕,程序继续运行
Java中如何进行异常处理

Java的异常处理是通过5个关键字来实现的:try、catch、finally、throw、throws

异常处理的三种方式

  1. 捕获异常 try-catch-finally
    try{代码逻辑}catch(Exception){异常处理逻辑}
    · try中一旦遇到异常,之后的代码不会再执行
    · 尽量包含少的代码进行捕获
    执行过程中,可能存在的情况:
    1)正常执行,只执行try中代码
    2)遇到异常情况,会处理try中异常代码之前的逻辑,后面不会执行,最后会执行catch
    3)使用多重catch的时候,会遇到异常子类不匹配的情况,此时依然会报错,因此建议在catch最后将所有异常的父类写上

    finally:在程序运行过程中,如果处理异常的部分包含finally的处理,那么无论代码是否发生异常,finally内内容总会执行
    finally包含的处理逻辑?
    1)IO流的关闭操作
    2)数据库的连接关闭操作
    注意:当使用多重catch时一定要注意相关异常的程序,将子类放在最前面的catch,父类放在后面的catch

  2. 声明异常 throws Exception
    1)多个异常用逗号隔开
    2)在异常情况出现的时候,可以使用try-catch-finally的方法处理异常,也可以将异常向外抛出,由外部进行处理。
    3)在方法调用过程中,可能存在n多个方法之间的调用。此时假如每个方法中都包含了异常情况,那么就需要在每个方法中都进行try-catch。另外一种比较简单的方式,就是在方法最外层调用处理一次即可。使用throws方法,对所有执行过程中的所有方法出现的异常进行统一集中处理。

    如何判断使用try catch还是throws?
    最稳妥方式是在每个方法中都进行异常的处理。
    偷懒的方式是判断在整个调用的过程中,外层的调用方法是否有对异常的处理。如果有直接使用throws,如果没有,就要使用try catch。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public static void main(String[] args) {
    try {
    test4(); // 如果只是throws,会直接抛给JVM,不会继续打印hehe
    } catch (Exception e) {
    e.printStackTrace();
    }
    System.out.println("hehe");
    }
    public static void test1() throws Exception {
    System.out.println(1/0);
    }
    public static void test2() throws Exception {
    test1();
    System.out.println(100/0);
    }
    public static void test3() throws Exception {
    test2();
    }
    public static void test4() throws Exception {
    test3();
    }

    结果:

    java.lang.ArithmeticException: / by zero
    at demo.Exception2.test1(Exception2.java:43)
    at demo.Exception2.test2(Exception2.java:46)
    at demo.Exception2.test3(Exception2.java:50)
    at demo.Exception2.test4(Exception2.java:53)
    at demo.Exception2.main(Exception2.java:24)
    hehe

    Process finished with exit code 0

    如果main中不使用try catch,而是声明异常,结果和对代码块不进行任何处理结果一样:

    java.lang.ArithmeticException: / by zero
    at demo.Exception2.test1(Exception2.java:43)
    at demo.Exception2.test2(Exception2.java:46)
    at demo.Exception2.test3(Exception2.java:50)
    at demo.Exception2.test4(Exception2.java:53)
    at demo.Exception2.main(Exception2.java:24)

    Process finished with exit code 1

  3. 抛出异常 throw

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // Exception2.java
    public static void main(String[] args) {
    try {
    show();
    } catch (Exception e) {
    e.printStackTrace();
    }
    System.out.println("hehe");
    }
    public static void show() throws Exception{
    String gender="1234";
    if (gender.equals("man")) {
    System.out.println("man");
    } else if (gender.equals("woman")) {
    System.out.println("woman");
    } else {
    //如果错误向外抛,main中会对异常进行捕获,捕获的异常信息就是下面这句话
    throw new Exception("性别出现错误");
    }
    }

    结果:

    java.lang.Exception: 性别出现错误
    at demo.Exception2.show(Exception2.java:38)
    at demo.Exception2.main(Exception2.java:24)
    hehe

    自定义异常
    在Java的API中提供了丰富异常类,但有时候不满足需求,就需要自定义。

    步骤
    1.继承Exception类
    2.自定义实现构造方法
    3.需要使用的时候,使用throw new自定义异常的名称

    什么时候需要自定义异常?
    一般情况下不需要,但在公司要求明确,或者要求异常格式规范统一的时候是要自己实现的

    案例
    自定义一个异常类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class GenderException extends Exception {
    public GenderException() {
    System.out.println("性别异常");
    }

    public GenderException(String msg) {
    System.out.println(msg);
    }
    }

    在Exception2.java中,修改else块内容为throw new GenderException("Gender is wrong");
    结果:

    Gender is wrong
    hehe
    demo.GenderException
    at demo.Exception2.show(Exception2.java:40)
    at demo.Exception2.main(Exception2.java:24)

异常的分类

所有异常的父类是Throwable,子类分为ErrorException
Error:系统级的错误。代码层面无法解决的问题称为“Error”。
Exception:代码层面解决的错误。

  1. CheckedException 编译时异常
    当编写的时候,IDE帮你进行自动编译的时候,划波浪线,自动提示出错,属于编译时异常
    必须捕获或者声明抛出
  2. RuntimeException 运行时异常
    运行时可能出错可能不出错(例如参数的输入不同导致结果正确与否)
    不要求捕获或者声明抛出,但尽可能做到减少运行时异常

相关题目

  1. try-catch块中存在return语句,是否还执行finally块,如果执行,说出执行顺序
    return关键字:1.返回对应的结果值 2.中断/关闭当前的程序
    1)try中有return,finally中没有return
    在这里插入图片描述
    “return num+=80”被拆分成“num+=80”和“return num”,先执行try中的“num+=80”,将结果保存起来,在try中“return num”执行前,先将finally中语句执行完,再将90返回。

    2)try和finally中均有return
    在这里插入图片描述
    try中语句同样被拆分了,finally中的“return num”语句先于try中的“return num”语句执行,因此try中的“return num”被“覆盖”了,不再执行。

    3)try里面和finally外面均有return,return的数据是基本数据类型或文本字符串
    在这里插入图片描述
    虽然在finally中改变了返回值num,但因为finally中没有return该num值,所以在执行完finally中的语句后,test()会得到try中返回的num值,而try中的num依然是程序进入finally代码块前保留下来的值。
    如果try中改成“return num+=80;”,最后结果10变成了90。

    4)try里面和finally外面均有return,return的数据是引用数据类型
    在这里插入图片描述
    总结
     try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中的语句,而后分为3种情况

    1. 如果finally中有return语句,则会将try中return语句“覆盖”掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。
    2. 如果finally中没有return语句,也没有改变要返回的值,则执行完finally中的return语句后,会接着执行try中的return语句,返回之前保留的值。
    3. 如果finally中没有return语句,但是改变了要返回的值,这里有的类似于引用传递和值传递的区别,分为以下两种情况
       ① 如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前的值。
       ② 如果return的数据是引用数据类型,则在finally中对引用该数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。
  2. try-catch-finally块中,finally唯一不执行的情况是什么?
    1)try之前异常,系统报错,当然不会执行finally
    2)try 或 catch块中,如果调用了退出虚拟机的方法(即System.exit(1);)会使程序提前退出

  3. 说出五个常见的运行时异常
    空指针异常,算数异常,数组下标越界异常,字符串下标越界异常,数字格式转换异常

  4. throw和throws的比较
    异:
    1)throws用来声明异常;throw用来抛出异常。
    2)throw位于函数体内,当程序出现某种逻辑错误时由程序员主动抛出了某种特定类型的异常;throws必须跟在方法参数列表的后面,表示该方法可能要抛出异常。
    3)throw抛出一个异常对象,且只能是一个;throws后面跟异常类,而且可以有多个。
    同:
    两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。