Java 的一个核心理念:在编译时就发现并处理可能的问题,而不是等到运行时才发现。

异常类型

每一个异常都是一个类, 且继承自 Exception 类。异常类型本质依然类的对象,但是异常类型支持在程序运行出现问题时抛出。

运行时异常(Runtime Exception)

在编译阶段无法感知代码是否会出现问题,只有在运行的时候才知道会不会出错(正常情况下是不会出错的),这样的异常称为运行时异常。

异常也是由类定义的,所有的运行时异常都继承自RuntimeException

受检异常(Checked Exception)

可以预见到的且必须要进行处理的一些异常,例如打开某个文件需要确保文件可以被访问,而这是在编写时不一定能保证。

因此 Java 将其写入规范,可能会出现受检异常的操作必须要被程序员手动编写处理逻辑,且会经过编译器检查,避免出现不小心忘记处理的情况。

默认继承自Exception类的异常都是编译时异常。

错误 Error

错误比异常更严重,异常不一定会导致致命的问题,而错误是致命问题,出现错误JVM将终止运行,比如OutOfMemoryError为内存溢出错误(内存占用已经超出限制,无法继续申请内存了)

自定义异常

自定义编译时异常

继承 Exception 即可:

public class TestException extends Exception {
	public TestException(String message) {
		super(message);
	}
}

自定义运行时异常

继承 RuntimeException 即可:

public class TestException extends RuntimeException {
	public TestException(String message) {
		super(message);
	}
}

RuntimeException 继承自 Exception, Exception 又继承自 Throwable

抛出异常

运行时异常不必在函数头标注。

public static int test(int a, int b) {
    if(b == 0)
        throw new RuntimeException("被除数不能为0");  //使用throw关键字来抛出异常
    return a / b;
}

异常的抛出需要创建一个异常对象出来,实际上就是抛出异常对象,异常对象携带了一些信息,比如是因为什么原因导致的异常,在RuntimeException的构造方法中可以写入原因。

如果有不同的异常分支, 也需要在函数头 throws 全部:

private static void test(int a) throws FileNotFoundException, ClassNotFoundException {  //多个异常使用逗号隔开
    if(a == 1)
        throw new FileNotFoundException();
    else 
        throw new ClassNotFoundException();
}

在重写方法时, 原方法的异常不必继承, 但如果新代码也会抛出异常就需要写上:

@Override
protected Object clone() {
    return new Object();
}

异常处理

检测到异常时, JVM 默认会直接终止程序运行,并在控制台打印堆栈错误追踪信息。

若我们想自己处理异常, 通过对异常进行捕获使得程序继续运行下去, 就需要用到 try-catch 语句:

public static void main(String[] args) {
    try {    //使用try-catch语句进行异常捕获
        Object object = null;
        object.toString();
    } catch (NullPointerException e){   //因为异常本身也是一个对象,catch中实际上就是用一个局部变量去接收异常
		e.printStackTrace();
    }
    System.out.println("程序继续正常运行!");
}

也可以想 elif 一样整多个, 但也存在短路问题, 即前面的是 RuntimeException 时, 后面的所有 RuntimeException 子类都会无法捕获到。

对于两种及多种异常用同一代码段的情况, 可以用 | 链接起来:

try {
     //....
} catch (NullPointerException | IndexOutOfBoundsException e) {  //用|隔开每种类型即可
        
}
 

catch 中需要指明所接受的异常类, 该类必须属于 Throwable 的子类, 通常 ExceptionError 都属于该类的子类。

使用 e.printStackTrace() 来输出错误堆栈跟踪, 用 e.getMessage() 来输出异常信息。

当我们希望,程序运行时,无论是否出现异常,都会在最后执行任务,可以交给finally语句块来处理:

try {
    //....
}catch (Exception e){
            
}finally {
      System.out.println("lbwnb");   //无论是否出现异常,都会在最后执行
}

如果省去 catch, 只用 try-finally, 那么程序依然会被JVM停止运行, 但停止运行前会执行 finally 的语句。

断言表达式

断言表达式需要使用到assert关键字,如果assert后面的表达式判断结果为false,将抛出AssertionError错误。

需要加 java 启动参数手动开启:

比如判断变量的值,如果大于10就抛出错误:

public static void main(String[] args) {
    int a = 10;
    assert a > 10 : "我是自定义的错误信息";
}