JAVA基础-异常

1,异常概述

异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。

2,异常的结构

1,Throwable

  在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。异常和错误的区别是:异常能被程序本身可以处理,错误是无法处理。

2,Error

  是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如 Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。

3,Exception

  是程序本身可以处理的异常。Exception 类有一个重要的子类RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。

  Exception(异常)分两大类:运行时异常和非运行时异常(编译异常)

1. 检查异常

//必须在方法名上 throws 或者 try-catch 捕获处理
public static void checkException() throws IOException {
    throw new IOException();
}

public static void checkException1() {
    try{
        throw new IOException();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

(1) 检查异常,编译器要求必须处置的异常:正确的程序在运行中,很容易出现的、情理可容的异常状况。
(2)除了 Exception 中的 RuntimeException 及 RuntimeException 的子类以外,其他的 Exception 类及其子类(例如:IOException和ClassNotFoundException)都属于可查异常。
(3)这种异常的特点是 Java 编译器会检查它,也就是说,当程序中可能出现这类异常,要么用 try-catch 语句捕获它,要么用 throws 子句声明抛出它,否则编译不会通过。

2. 非检查异常

//可以不用理会,抛异常程序直接挂了
public static void runtimeException(){
    throw new RuntimeException();
}

public static void error(){
    throw new NoClassDefFoundError();
}

(1)不可查异常(编译器不要求强制处置的异常)**:包括运行时异常(RuntimeException与其子类)和错误(Error)。
(2)RuntimeException 表示编译器不会检查程序是否对 RuntimeException 作了处理,在程序中不必捕获 RuntimException 类型的异常,也不必在方法体声明抛出 RuntimeException 类。RuntimeException 发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获 RuntimeException。

2,异常处理

1,throws

如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。


Throws抛出异常的规则:

  1. 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
  2. 如果一个方法可能出现可查异常(checked exception),要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误。
  3. 只有当抛出了异常时,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出。
  4. 调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。

2,throw

  throw总是出现在方法体中,用来抛出一个Throwable类型的异常。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。

3,try-catch-finally

  在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

一般语法为:

try {  
    // 可能会发生异常的程序代码  
} catch (Type1 id1){  
    // 捕获并处置try抛出的异常类型Type1  
} catch (Type2 id2){  
     //捕获并处置try抛出的异常类型Type2  
}finally {  
    // 无论是否发生异常,都将执行的语句块  
} 

  Java通过异常类描述异常类型,对于有多个catch子句的异常程序而言,应该尽量将捕获底层异常类的catch子句放在前面,同时尽量将捕获相对高层的异常类的catch子句放在后面。否则,捕获底层异常类的catch子句将可能会被屏蔽。

  无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇 到return语句时,finally语句块将在方法返回之前被执行。

在以下4种特殊情况下,finally块不会被执行:

  1. 在finally语句块中发生了异常。
  2. 在前面的代码中用了System.exit()退出程序。
  3. 程序所在的线程死亡。
  4. 关闭CPU。

4,SneakyThrows 注解

@SneakyThrows 就是利用了这一机制,将当前方法抛出的异常,包装成 RuntimeException ,骗过编译器,使得调用点可以不用显示处理异常信息。

5. 异常链处理

RuntimeException 四个构造方法:最终都是使用 Throwable 的构造方法

public RuntimeException() {
    super();
}

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

public RuntimeException(String message, Throwable cause) {
    super(message, cause);
}

public RuntimeException(Throwable cause) {
    super(cause);
}

Throwable 的构造方法:

public Throwable() {
    fillInStackTrace();
}

public Throwable(String message) {
    fillInStackTrace();
    detailMessage = message;
}

public Throwable(String message, Throwable cause) {
    fillInStackTrace();
    detailMessage = message;
    this.cause = cause;
}

public Throwable(Throwable cause) {
    fillInStackTrace();
    detailMessage = (cause==null ? null : cause.toString());
    this.cause = cause;
}

主要两个参数,message 异常信息,就直接放到异常的 message 属性。cause 则也是一个 Throwable,表示引起当前异常的原因,因此就形成一个异常链,我们可以使用递归的方式拿到最终导致这个异常的问题。

public static Throwable getCause(Throwable t){
    if(t.getCause() != null){
        return getCause(t.getCause());
    }else{
        return t;
    }
}

6,需要注意的问题

  1. 尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常,在这里是 Thread.sleep() 抛出的 InterruptedException。
  2. 不要生吞(swallow)异常。这是异常处理中要特别注意的事情,因为很可能会导致非常难以诊断的诡异情况。
  3. try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码
  4. 与此同时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(if/else、switch)要低效。
  5. printStackTrace()使用的是synchronized锁,性能低,吃程序资源,容易造成程序资源占满进而导致程序崩溃。建议使用第三方日志依赖如:logback(springboot首选)、log4j、log4j2
posted @ 2024-02-19 10:36  primaryC  阅读(3)  评论(0编辑  收藏  举报