Java异常基础
1. 异常
异常的概念:程序在运行过程中出现的不正常现象
2. 异常的分类
- Throwable: 可抛出的,一切错误或异常的父类
- Error(JVM、硬件、执行逻辑错误,不能手动处理)
- StackOverflowError
- OutofMemoryError
- Exception(程序在运行和配置中产生的问题,可处理)
- RuntimeException: 运行时异常,可处理,可不处理
- CheckedException: 检查时异常,必须处理,例如:FileNotFoundException, ClassNotFoundException
- Error(JVM、硬件、执行逻辑错误,不能手动处理)
2.1 异常(Exception)
- Exception 分为两大类:CheckedException(可检查异常)和 UncheckedException(即:RuntimeException 运行时异常)
- 对于CheckedException,Java要求必须显式捕获并处理该异常,或者显式声明抛出该异常,不然编译不通过
- UncheckedException 通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求
当遇到 RuntimeException 时,通常的做法是:程序先捕获原始异常,然后抛出一个新的业务异常,新的业务异常中包含了对用户的提示信息,这种处理方式被称为异常转译
异常链:捕获一个异常然后接着抛出另一个异常,并把原始异常信息保存下来,是一种典型的链式处理(职责链模式)
2.2 常见的运行时异常
NullPointerException, ArrayIndexOutofBoundsException, ClassCastException, NumberFormatException, ArithmeticException
// java.lang.NullPointerException
String name = null;
System.out.println(name.equals(""));
// java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
Object obj = "exception";
Integer value = (Integer) obj;
// java.lang.NumberFormatException: For input string: "exception"
Integer value = Integer.valueOf("demo");
// java.lang.ArithmeticException: / by zero
int value = 10 / 0;
3. 异常处理机制
异常的传递:按照方法的调用链反向传递,如果始终没有处理异常,最终会由 JVM 处理(打印堆栈跟踪信息)
Java 的异常处理是通过 5 个关键字来实现的:
try: 执行可能产生异常的代码
catch: 捕获异常,并处理
finally: 无论是否发生异常,代码总能执行;finlly 唯一不执行的情况是退出 JVM 虚拟机
throw: 手动抛出异常
throws: 声明方法可能要抛出的各种异常
3.1 异常处理
try {
// 手动退出 JVM
// System.exit(0);
} catch(Exception e) {
} finally {
}
3.2 多重 catch
(1)子类异常在前,父类异常在后
(2)发生异常时按顺序逐个匹配
(3)只执行第一个与异常类型匹配的 catch 语句
进行异常捕获时,一定要记住先捕获小异常,再捕获大异常
public void test(String str) throws RuntimeException {
try {
if (str == null) {
throw new RuntimeException("NPE");
}
} catch (RuntimeException e) {
throw e;
} catch (Throwable t) {
throw t;
}
}
3.3 try...finally
try...finally 不能捕获异常,仅仅用来当发生异常时,用来释放资源
一般用在底层代码,只释放资源不做异常处理,把异常向上抛出
4. Java 对异常处理的两种方式
4.1 方式一:声明异常
使用原则:底层代码向上声明或者抛出异常,最上层一定要处理异常,否则程序中断
throws 声明异常:method() throws Exception
throw 抛出异常:throw new Exception()
4.1.1 throws
- throws 用在方法声明(方法签名)上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常
- 方法内部抛出编译器异常
- 使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理;如果main方法也不知道如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常将交给JVM处理
4.1.2 throw
- 用来抛出一个指定的异常对象
- 语法:throw 异常对象;
- 必须写在方法内部,必须是 Exception 或 Exception 的子类对象
- throw语句可以单独使用,throw语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例
如果抛出的是 Exception 异常,还需要在方法后面声明异常
public static void test() throws Exception {
try {
int i = 10 / 0;
} catch (Exception e) {
throw new Exception(e);
}
}
4.2 方式二:捕获异常
- try...catch...finally
- finally(无论异常是否发生,都需要执行)
try {
test();
} catch (Exception e) {
// 打印整个异常栈
log.info("xxx", e);
// org.apache.commons.lang3.exception.ExceptionUtils
log.info(ExceptionUtils.getStackTrace(e));
// Java PrintWriter
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
log.info(sw.toString());
} finally {
System.out.println("finally...");
}
private static void test() {
throw new NullPointerException("NPE...");
}
5. 自定义异常
- 需继承自 Exception 或 Exception 的子类,常用 RuntimeException
- 必要提供的两个构造方法:
- 无参构造器
- 带一个字符串参数的构造器
public MyException(String message) {}
5.1 异常方法的覆盖(重写)
- 子类中的方法,不能抛出比父类更多、更宽的检查时异常
- 对于 RuntimeException,抛什么都可以