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
的子类, 通常 Exception
和 Error
都属于该类的子类。
使用 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 : "我是自定义的错误信息";
}