023、异常

Par @Martin dans le
Tags :

异常体系

Java 把异常当作对象来处理, 并定义一个基类 java.lang.Throwable 作为所有异常的超类(父类), 有两个子类 ErrorException, 分别表示错误和异常.

  • Error 是程序无法处理的错误, 比如 OutOfMemoryError、ThreadDeath 等, 这些异常发生时, JVM 一般会选择线程终止, 所以你最好期望不要出现这类异常.
  • Exception 是程序本身可以处理的异常, 程序中应当尽可能去处理这些异常.

我们主要讨论的是 Exception.

Exception 也分为两大类: 运行异常(RuntimeException)编译异常.

运行异常 都是 RuntimeException 类及其子类异常, 如 NullPointerException、IndexOutOfBoundsException 等, 这些异常是不检查异常, 程序中可以选择捕获处理, 也可以不处理, 这些异常一般是由程序逻辑错误引起的, 程序应该从逻辑角度尽可能避免这类异常的发生.

编译异常 是 RuntimeException 以外的异常, 从程序语法角度讲是必须进行处理的异常, 如果不处理, 程序就不能编译通过, 如 IOException、SQLException 等以及用户自定义的 Exception.

Throwable

Throwable 常用方法:

getCause(): 返回抛出异常的原因, 如果 cause 不存在或未知, 则返回 null;

getMessage(): 返回异常的消息信息.

printStackTrace(): 对象的堆栈跟踪输出至错误输出流, 作为字段 System.err 的值.

toString(): 返回此 Throwable 的简短描述

Throwable 构造函数:

Throwable(): 构造一个将 null 作为其详细消息的新 throwable

Throwable(String message): 构造带指定详细消息的新 throwable

Throwable(Throwable cause): 构造一个带指定 cause 和 (cause==null ? null :cause.toString())(它通常包含类和 cause 的详细消息)的详细消息的新 throwable

Throwable(String message, Throwable cause): 构造一个带指定详细消息和 cause 的新 throwable

异常处理

在 Java 中, 使用 try-catchtry-catch-finally 来捕获并处理异常.

try {
     // 一些会抛出异常的方法
} catch (Exception e) {
     // 该异常处理代码
}

例子:

try< {
    System.out.print("请输入你的年龄:");
    Scanner input = new Scanner(System.in);
    int age = input.newtInt();
    System.out.println("十年后你" + (age + 10) + "岁");
} catch (InputMismatchException e) {
    System.out.println("你应该输入整数!");
}
System.out.println("程序结束!");

如果在 int age = input.newtInt() 这里输入了非整数的其他字符, 如 hello, 那么 System.out.println(“十年后你” + (age + 10) + “岁”) 这包就不会被执行, 程序会跳到 catch 语句中, try-catch 执行完后, 后续的语句会继续执行.

try {
     // 一些会抛出异常的方法
} catch (Exception e) {
     // 该异常处理代码
} finally {
     // 最终要执行的一些代码
}

何为 finally?

先看一个没有 finally 的异常处理 try-catch 语句:
假设 count 为要使用到的资源, 并且用完要求释放此资源, 那么我们可以把释放资源的语句放到 try-catch 后执行, 当前的程序不管是在执行完 try 语句块还是 catch 语句块, 都会顺序执行到下面释放资源的语句.

int count = 0; // 初始化资源
try {
    count++;
    // 一些会抛出异常的方法
} catch (Exception e) {
    // 异常处理
}
count = 0; //释放资源

这段代码是没问题的, 但是, 如果在 try 或 catch 中有 return 语句, 那么在每条 return 语句之前, 都要先执行释放资源的语句, 这样, 就需要在每一个可能返回的地方, 考虑如何释放资源, 最终将导致代码的复杂和冗余.
所以, 需要 finally 语句, 把资源释放或状态还原的代码放到 finally 块中, finally 语句是在 return 语句执行之后、return 返回之前(返回到调用函数之前)执行, 此时, 就不需要再考虑各种复杂的资源释放情况.

system.exit() 是唯一可以中断 finally 的命令.

自定义异常

class 自定义异常类 extends 异常类型 {

    // 构造函数
    public 自定义异常类(String info) {
        super(info); // 调用 父类(String message): 构造带指定详细消息的新异常
    }
}

throw、throws

throw 关键字是用于__方法体内部__, 用来抛出一个 Throwable 类型的异常;

  • 如果抛出了检查异常, 则还应该在方法头部声明方法可能抛出的异常类型, 该方法的调用者也必须检查处理抛出的异常
  • 如果抛出的是 Error 或 RuntimeException, 则该方法的调用者可选择处理该异常
  • 如果所有方法都层层上抛获取的异常, 最终 JVM 会进行处理, 处理也很简单, 就是打印异常消息和堆栈信息

throws 关键字用于__方法声明部分__, 用来声明方法可能会抛出某些异常, 仅当抛出了__检查异常__, 该方法的调用者才必须处理或者重新抛出该异常, 当方法的调用者无力处理该异常的时候, 应该继续抛出, 而不是囫囵吞枣, 一般在 catch 块中打印一下堆栈信息做个勉强处理.

下面给出一个简单例子, 看看如何 使用这两个关键字:

public static void test() throws Exception {
     // 抛出一个检查异常
     throw new Exception("方法 test 中的Exception");
}

之前说到: 如果抛出了检查异常, 则该方法的调用者也必须检查处理抛出的异常;
所以要调用 test() 的方法就必须处理 test() 抛出的异常或者添加 throws 声明将将异常继续上抛.

// 处理 test() 抛出的异常
public void compute_1() {
    try {
        test()
    } catch (Exception e) {
        System.out.println("e.getMessage()");
    }
}

// 或者添加 throws 声明, 将异常上抛
public void compute_2() throws Exception {
    test()
}

重写的异常

重写方法, 如果也要抛异常, 则只能抛出被覆盖的方法中同样的异常或者异常的子类.