Java 异常处理

133 阅读9分钟

1. 异常概述

指的是程序在执行过程中,出现的非正常情况,如果不处理最终会导致 JVM 的非正常停止。

1.1 抛出机制

Java 中把不同的异常用不同的类表示,一旦发生某种异常,就创建该异常类型的对象,并且抛出 (throw)。然后程序员可以捕获 (catch) 到这个异常对象并处理;如果没有捕获 (catch) 这个异常对象,那么这个异常对象将会导致程序终止。

1.2 如何对待异常

对于程序出现的异常,一般有两种解决方法: 一是遇到错误就终止程序的运行。
另一种方法是程序员在编写程序时,就充分考虑到各种可能发生的异常和错误,极力预防和避免。实在无法避免的,要编写相应的代码进行异常的检测、以及异常的处理,保证代码的健壮性。

1.3 异常体系

java.Lang.Throwable:异常体系的根父类
---java.lang.Error:错误。Java虚拟机无法解决的严重问题。如,JVM系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。

StackOverfLowError(栈溢出)

 public static void method1() {
        method1();
    }
Exception in thread "main" java.lang.StackOverflowError

0utOfHemoryError(内存溢出)

// 10M    1024*1024*10 B
        byte[] bytes = new byte[1024 * 1024 * 12];
        System.out.println(bytes.length);
// 堆 内存溢出
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.qy40.edu.day0210.excep.ExceptionExercise.main(ExceptionExercise.java:6)

---java.lang.Exception:异常。我们可以编写针对性的代码进行处理。

编译时异常:(受检异常)在执行javac.exe命令时,出现的异常。
1、找不到类异常(ClassNotFoundException)
2、IO异常(IOException):即:Input、Output,我们在读写磁盘文件、网络内容的时候经常会生的一种异常,这种异常是受检查异常,需要进行手工捕获。
3、找不到文件异常(FileNotFoundException):如果文件不存在就会抛出这种异常。

运行时异常:(非受检异常)在执行java.exe命令时,出现的异常。
1、空指针异常(NullPointException):当对象不存在,却又去调用对象的属性或方法时,就会出现该异常。
2、数组越界异常(ArrayIndexOutOfBoundsException):当数组只存在5个元素,他们所对应的的下标即为0-4,如果访问数组下标为5的元素时,就会提示该异常,原因是该位置元素不存在。
3、强制类型转换异常(ClassCastException):在进行类型转换时,如果两个对象类型不匹配,无法进行转换时,就会出现该异常。
4、数字格式化异常(NumberFormatException):在进行数组类型转换时,因为格式的原因以至于无法正常转换的异常。
5、输入类型不匹配异常(InputMismatchException):使用Scanner时输入的类型和接收输入的类型不一致导致。
6、算数运算符异常(ArithmeticException):出现异常的运算条件时,会出现该异常。例如:3/0。

2. 异常的处理

2.1 try..catch..finally处理(抓抛模型)

2.1.1 try..catch基本使用

①将可能出现异常的代码声明在 try 语句中。一旦代码出现异常,就会自动生成一个对应异常类的对象。并将此对象抛出,针对于 try 中抛出的异常类的对象,使用之后的 catch 语句进行匹配。一旦匹配上,就进入 catch 语句块进行处理。一旦处理接触,代码就可继续向下执行。
②如果声明了多个 catch 结构,不同的异常类型在不存在子父类关系的情况下,谁声明在上面,谁声明在下面都可以。
③如果多个异常类型满足子父类的关系,则必须将子类声明在父类结构的上面。否则,报错。
④try中声明的变量,出了try结构之后,就不可以进行调用了。

/**
     * try...catch 基本使用
     */
    private static void method_01() {
        try {
            //有可能出现异常的代码块 放到try
            //当出现异常之后 直接跳出try块
            System.out.println(1 / 0);
            System.out.println("13 line++++++++++++++++++++++");
            //catch 的异常类型 一定要和异常对象类型 匹配; 背后有一个异常对象
        } catch (ArithmeticException e) { //RuntimeException e = new ArithmeticException();
            //对异常进行处理 一般都要记录log 日志文件
            System.out.println("catch 代码块");
            System.out.println(e);
            System.out.println("message: " + e.getMessage());
            //打印栈的报错信息
            e.printStackTrace();
        }
        System.out.println("end");
    }

编译时异常的处理

catch中异常处理的方式:
①自己编写输出的语句。
②printstackTrace():打印异常的详细信息。
③getMessage():获取发生异常的原因。

/**
     * 编译时异常的处理
     */
    private static void method7() {
        //编译异常 一定要处理 编译才能通过
        try {
            FileInputStream fileInputStream = new FileInputStream("abc");
        } catch (FileNotFoundException e) {
            System.out.println(e);
            e.printStackTrace();
        }
    }
2.1.2 多重catch
 /**
     * 多重catch
     * 小类型异常在上面,大类型异常在下面
     *
     * @param str
     */
    private static void method8(String[] str) {
        try {
            int i = Integer.parseInt(str[1]);
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (NullPointerException e) {
            e.printStackTrace();
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("end");
    }

2.2 finally

2.2.1 finally的理解

我们将一定要被执行的代码声明在 finally 结构中。
更深刻的理解:无论 try 中或 catch 中是否存在仍未被处理的异常,无论 try 中或 catch 中是否存在 return 语句等,finall 中声明的语句都一定要被执行。
finally 语句和 catch 语句是可选的,但 finally 不能单独使用。

2.2.2 什么样的代码我们一定要声明在finally中呢?

我们在开发中,有一些资源(比如,输入,输出流,数据库连接、Scket连接等资源》,在使用完以后,必须显式的进行关闭操作。否则,系统不会自动的回收这些资源。进而导致内存的泄漏。
为了保证这些资源在使用完以后,不管是否出现了未被处理的异常的情况下,这些资源能被关闭。我们必须将这些操作声明在 finally 中!

    \* finally 代码块
   private static void method9() {
       try {
           //System.out.println(1 / 0);
           System.out.println("start");
           //就算碰到return finally也会执行
           // return;
       } catch (NullPointerException e) {
           e.printStackTrace();
           //最终 绝对会执行的代码
       } finally {
           // 资源的释放
           System.out.println("finally");
       }
   }

2.3 throws(抛出异常)

2.3.1 格式:

使用"throws 异常类型1,异常类型2,..."

2.3.2 是否真正处理了异常?

从编译是否能通过的角度看,看成是给出了异常万一要是出现时候的解决方案。此方案就是,继续向上抛出 (throws)。
但是,此 throws 的方式,仅是将可能出现的异常抛给了此方法的调用者。此调用者仍然需要考虑如何处理相关异常。从这个角度来看,throws的方式不算是真正意义上处理了异常。

2.3.3 会抛出异常的方法
     * 如果是 运行时异常 JVM会自动抛出去 不需要显示抛;
    // throws 会方法声明处 表示此方法会抛出的异常类型
    private static void method10(String str) throws FileNotFoundException, ClassNotFoundException {
        if (str == null) {
            FileNotFoundException exception = new FileNotFoundException();
            // throw 用在方法体内部 抛异常对象;
            // 一旦throw执行了 当前方法就结束了 和return效果一致
            throw exception;
        }
        if (str.length() == 2) {
            throw new ClassNotFoundException();
        }
    }
方法的调用
       public static void main(String[] args) {
            //JVM 会自动创建的是运行时异常对象;
            try {
                method10("");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
2.3.4 方法的重写的要求:(针对于编译时异常来说的)

子类重写的方法抛出的异常类型可以与父类被重写的方法抛出的异常类型相同,或是父类被重写的方法抛出的异常类型的子类。

2.4 开发中,如何选择异常处理的两种方式?

①如果程序代码中,涉及到资源的调用(流、数据库连接、网络连接等),则必须考虑使用try-catch-finally来处理保证不出现内存泄漏。
②如果父类被重写的方法没有throws异常类型,则子类重写的方法中如果出现异常,只能考虑使用try-catch-finally进行处理,不能throws。
③开发中,方法a中依次调用了方法b,c,d等方法,方法b,c,d之间是递进关系。此时,如果方法b,c,d中有异常我们通常选择使用throws,而方法a中通常选择使用try-catch-finally。

3. 手动异常处理(throw)

在实际开发中,如果出现不满足具体场景的代码问题,我们就有必要手动抛出一个指定类型的异常对象。

3.1 如何理解“自动 vs 手动”抛出异常对象?

过程1:”抛”
"自动抛":程序在执行的过程当中,一旦出现异常,就会在出现异常的代码处,自动生成对应异常类的对象,并将此对象抛出。
"手动抛":程序在执行的过程当中,不满足指定条件的情况下,我们主动的使用"throw + new + 异常类的对象"方式抛出异常

过程2:“抓”
狭义上讲:try-catch的方式捕获异常,并处理。
广义上讲:把“抓”理解为“处理”。则此时对应着异常处理的两种方式:① try-catch-finally ② throws

3.2 如何实现手动抛出异常?

在方法内部满足指定条件的情况下,使用"throw new 异常类的对象”的方式抛出

4. 自定义异常

4.1 如何自定义异常类?

①继承于现有的异常体系。通常继承于RuntimeException/Exception
②通常提供几个重载的构造器
③提供一个全局常量,声明为:static final long serialVersionUID =

4.2 如何使用自定义异常类?

在具体的代码中,满足指定条件的情况下,需要手动的使用"throw + 自定义异常类的对象"方式,将异常对象抛出。如定义异常类是非运行时异常,则必须考虑如何处理此异常类的对象。(具体的: try-catch-finally 、throws)

定义异常:

public class KeyNotFoundException extends Exception {
    static final long serialVersionUID = -51252315L;
    public KeyNotFoundException() {
    }

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

使用:

 public static void main(String[] args) {
        try {
            method12(null, "");
        } catch (KeyNotFoundException e) {
            // e.printStackTrace();
           System.out.println(e.getMessage());

        }
    }


    private static void method12(String key, String value) throws KeyNotFoundException {
        if (key == null) {
            throw new KeyNotFoundException("key键找不到");
        }
    }
4.3 为什么需要自定义异常类?

我们其实更关心的是,通过异常的名称就能直接判断此异常出现的原因。既然如此,我们就有必要在实际开发场景中不满足我们指定的条件时,指明我们自己特有的异常类。通过此异常类的名称,就能判断出具体出现的问题。