:notebook: 本文已归档到:「 blog 」
:keyboard: 本文中的示例代码已归档到:「 javacore 」
Throwable 是 Java 语言中所有错误( Error )和异常( Exception )的超类。 Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。
主要方法:
fillInStackTrace - 用当前的调用栈层次填充 Throwable 对象栈层次,添加到栈层次任何先前信息中。 getMessage - 返回关于发生的异常的详细信息。这个消息在 Throwable 类的构造函数中初始化了。 getCause - 返回一个 Throwable 对象代表异常原因。 getStackTrace - 返回一个包含堆栈层次的数组。下标为 0 的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。 printStackTrace - 打印 toString() 结果和栈层次到 System.err ,即错误输出流。 toString - 使用 getMessage 的结果返回代表 Throwable 对象的字符串。 Error 是 Throwable 的一个子类。** Error 表示合理的应用程序不应该尝试捕获的严重问题。**大多数此类错误都是异常情况。 编译器不会检查 Error 。
常见 Error :
AssertionError VirtualMachineError UnsupportedClassVersionError StackOverflowError OutOfMemoryError
Exception 是 Throwable 的一个子类。 Exception 表示合理的应用程序可能想要捕获的条件。
**编译器会检查 Exception 异常。**此类异常,要么通过 throws 进行声明抛出,要么通过 try catch 进行捕获处理,否则不能通过编译。
常见 Exception :
ClassNotFoundException CloneNotSupportedException IllegalAccessException InstantiationException InterruptedException NoSuchFieldException NoSuchMethodException
示例:
public class ExceptionDemo {
public static void main(String[] args) {
Method method = String.class.getMethod("toString", int.class);
}
};
复制代码
试图编译运行时会报错:
Error:(7, 47) java: 未报告的异常错误java.lang.NoSuchMethodException; 必须对其进行捕获或声明以便抛出 复制代码
RuntimeException 是 Exception 的一个子类。 RuntimeException 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。
**编译器不会检查 RuntimeException 异常。**当程序中可能出现这类异常时,倘若既没有通过 throws 声明抛出它,也没有用 try catch 语句捕获它,程序还是会编译通过。
示例:
public class RuntimeExceptionDemo {
public static void main(String[] args) {
// 此处产生了异常
int result = 10 / 0;
System.out.println("两个数字相除的结果:" + result);
System.out.println("----------------------------");
}
};
复制代码
运行时输出:
Exception in thread "main" java.lang.ArithmeticException: / by zero at io.github.dunwu.javacore.exception.RumtimeExceptionDemo01.main(RumtimeExceptionDemo01.java:6) 复制代码
常见 RuntimeException :
ArrayIndexOutOfBoundsException ArrayStoreException ClassCastException IllegalArgumentException IllegalMonitorStateException IllegalStateException IllegalThreadStateException IndexOutOfBoundsException NegativeArraySizeException NullPointerException NumberFormatException SecurityException StringIndexOutOfBoundsException UnsupportedOperationException
Exception 或 RuntimeException 即可。 示例:
public class MyExceptionDemo {
public static void main(String[] args) {
throw new MyException("自定义异常");
}
static class MyException extends RuntimeException {
public MyException(String message) {
super(message);
}
}
}
复制代码
输出:
Exception in thread "main" io.github.dunwu.javacore.exception.MyExceptionDemo$MyException: 自定义异常 at io.github.dunwu.javacore.exception.MyExceptionDemo.main(MyExceptionDemo.java:9) 复制代码
如果想在程序中明确地抛出异常,需要用到 throw 和 throws 。
如果一个方法没有捕获一个检查性异常,那么该方法必须使用 throws 关键字来声明。 throws 关键字放在方法签名的尾部。
throw 示例:
public class ThrowDemo {
public static void f() {
try {
throw new RuntimeException("抛出一个异常");
} catch (Exception e) {
System.out.println(e);
}
}
public static void main(String[] args) {
f();
}
};
复制代码
输出:
java.lang.RuntimeException: 抛出一个异常 复制代码
也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。
throws 示例:
public class ThrowsDemo {
public static void f1() throws NoSuchMethodException, NoSuchFieldException {
Field field = Integer.class.getDeclaredField("digits");
if (field != null) {
System.out.println("反射获取 digits 方法成功");
}
Method method = String.class.getMethod("toString", int.class);
if (method != null) {
System.out.println("反射获取 toString 方法成功");
}
}
public static void f2() {
try {
// 调用 f1 处,如果不用 try catch ,编译时会报错
f1();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
f2();
}
};
复制代码
输出:
反射获取 digits 方法成功 java.lang.NoSuchMethodException: java.lang.String.toString(int) at java.lang.Class.getMethod(Class.java:1786) at io.github.dunwu.javacore.exception.ThrowsDemo.f1(ThrowsDemo.java:12) at io.github.dunwu.javacore.exception.ThrowsDemo.f2(ThrowsDemo.java:21) at io.github.dunwu.javacore.exception.ThrowsDemo.main(ThrowsDemo.java:30) 复制代码
throw 和 throws 的区别:
**使用 try 和 catch 关键字可以捕获异常。**try catch 代码块放在异常可能发生的地方。
它的语法形式如下:
try {
// 可能会发生异常的代码块
} catch (Exception e1) {
// 捕获并处理try抛出的异常类型Exception
} catch (Exception2 e2) {
// 捕获并处理try抛出的异常类型Exception2
} finally {
// 无论是否发生异常,都将执行的代码块
}
复制代码
此外,JDK7 以后, catch 多种异常时,也可以像下面这样简化代码:
try {
// 可能会发生异常的代码块
} catch (Exception | Exception2 e) {
// 捕获并处理try抛出的异常类型
} finally {
// 无论是否发生异常,都将执行的代码块
}
复制代码
try - try 语句用于监听。将要被监听的代码(可能抛出异常的代码)放在 try 语句块之内,当 try 语句块内发生异常时,异常就被抛出。 catch - catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时, try 后面的 catch 块就会被检查。 finally - finally 语句块总是会被执行,无论是否出现异常。 try catch 语句后不一定非要 finally 语句。 finally 常用于这样的场景:由于 finally 语句块总是会被执行,所以那些在 try 代码块中打开的,并且必须回收的物理资源(如数据库连接、网络连接和文件),一般会放在 finally 语句块中释放资源。 try 、 catch 、 finally 三个代码块中的局部变量不可共享使用。 catch 块尝试捕获异常时,是按照 catch 块的声明顺序从上往下寻找的,一旦匹配,就不会再向下执行。因此,如果同一个 try 块下的多个 catch 异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面。 示例:
public class TryCatchFinallyDemo {
public static void main(String[] args) {
try {
// 此处产生了异常
int temp = 10 / 0;
System.out.println("两个数字相除的结果:" + temp);
System.out.println("----------------------------");
} catch (ArithmeticException e) {
System.out.println("出现异常了:" + e);
} finally {
System.out.println("不管是否出现异常,都执行此代码");
}
}
};
复制代码
运行时输出:
出现异常了:java.lang.ArithmeticException: / by zero 不管是否出现异常,都执行此代码 复制代码
异常链是以一个异常对象为参数构造新的异常对象,新的异常对象将包含先前异常的信息。
通过使用异常链,我们可以提高代码的可理解性、系统的可维护性和友好性。
我们有两种方式处理异常,一是 throws 抛出交给上级处理,二是 try…catch 做具体处理。try…catch 的 catch 块我们可以不需要做任何处理,仅仅只用 throw 这个关键字将我们封装异常信息主动抛出来。然后在通过关键字 throws 继续抛出该方法异常。它的上层也可以做这样的处理,以此类推就会产生一条由异常构成的异常链。
示例:
public class ExceptionChainDemo {
static class MyException1 extends Exception {
public MyException1(String message) {
super(message);
}
}
static class MyException2 extends Exception {
public MyException2(String message, Throwable cause) {
super(message, cause);
}
}
public static void f1() throws MyException1 {
throw new MyException1("出现 MyException1");
}
public static void f2() throws MyException2 {
try {
f1();
} catch (MyException1 e) {
throw new MyException2("出现 MyException2", e);
}
}
public static void main(String[] args) throws MyException2 {
f2();
}
}
复制代码
输出:
Exception in thread "main" io.github.dunwu.javacore.exception.ExceptionChainDemo$MyException2: 出现 MyException2 at io.github.dunwu.javacore.exception.ExceptionChainDemo.f2(ExceptionChainDemo.java:29) at io.github.dunwu.javacore.exception.ExceptionChainDemo.main(ExceptionChainDemo.java:34) Caused by: io.github.dunwu.javacore.exception.ExceptionChainDemo$MyException1: 出现 MyException1 at io.github.dunwu.javacore.exception.ExceptionChainDemo.f1(ExceptionChainDemo.java:22) at io.github.dunwu.javacore.exception.ExceptionChainDemo.f2(ExceptionChainDemo.java:27) ... 1 more 复制代码
扩展阅读:https://juejin.im/post/5b6d61e55188251b38129f9a#heading-10
这篇文章中对于异常链讲解比较详细。
Java 异常处理中 finally 中的 return 会覆盖 catch 代码块中的 return 语句和 throw 语句,所以 Java 不建议在 finally 中使用 return 语句 。
此外 finally 中的 throw 语句也会覆盖 catch 代码块中的 return 语句和 throw 语句。
示例:
public class FinallyOverrideExceptionDemo {
static void f() throws Exception {
try {
throw new Exception("A");
} catch (Exception e) {
throw new Exception("B");
} finally {
throw new Exception("C");
}
}
public static void main(String[] args) {
try {
f();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
复制代码
输出:C
当子类重写父类带有 throws 声明的函数时,其 throws 声明的异常必须在父类异常的可控范围内——用于处理父类的 throws 方法的异常处理器,必须也适用于子类的这个带 throws 方法 。这是为了支持多态。
示例:
public class ExceptionOverrideDemo {
static class Father {
public void start() throws IOException {
throw new IOException();
}
}
static class Son extends Father {
@Override
public void start() throws SQLException {
throw new SQLException();
}
}
public static void main(String[] args) {
Father obj1 = new Father();
Father obj2 = new Son();
try {
obj1.start();
obj2.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
复制代码
上面的示例编译时会报错,原因在于:
因为 Son 类抛出异常的实质是 SQLException ,而 IOException 无法处理它。那么这里的 try catch 就不能处理 Son 中的异常了。多态就不能实现了。
如果 Java 程序只有一个线程,那么没有被任何代码处理的异常会导致程序终止。如果 Java 程序是多线程的,那么没有被任何代码处理的异常仅仅会导致异常所在的线程结束。
ArithmeticException ,就应该 catch ArithmeticException ,而不是 catch 范围较大的 RuntimeException ,甚至是 Exception 。 finally 块抛出异常或者返回值 扩展阅读:
(2) Error 是 Throwable 的一个子类。** Error 表示合理的应用程序不应该尝试捕获的严重问题。**大多数此类错误都是异常情况。 编译器不会检查 Error 。
(3) Exception 是 Throwable 的一个子类。** Exception 表示合理的应用程序可能想要捕获的条件。编译器会检查 Exception 异常。**此类异常,要么通过 throws 进行声明抛出,要么通过 try catch 进行捕获处理,否则不能通过编译。
(4) RuntimeException 是 Exception 的一个子类。 RuntimeException 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。**编译器不会检查 RuntimeException 异常。**当程序中可能出现这类异常时,倘若既没有通过 throws 声明抛出它,也没有用 try catch 语句捕获它,程序还是会编译通过。
(6)如果想在程序中明确地引发异常,则需要用到 throw 和 throws 。
try - try 语句用于监听。将要被监听的代码(可能抛出异常的代码)放在 try 语句块之内,当 try 语句块内发生异常时,异常就被抛出。 catch - catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时, try 后面的 catch 块就会被检查。 finally - finally 语句块总是会被执行,无论是否出现异常。 try catch 语句后不一定非要 finally 语句。 finally 常用于这样的场景:由于 finally 语句块总是会被执行,所以那些在 try 代码块中打开的,并且必须回收的物理资源(如数据库连接、网络连接和文件),一般会放在 finally 语句块中释放资源。 try 、 catch 、 finally 三个代码块中的局部变量不可共享使用。 catch 块尝试捕获异常时,是按照 catch 块的声明顺序从上往下寻找的,一旦匹配,就不会再向下执行。因此,如果同一个 try 块下的多个 catch 异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面。 语法示例:
try {
// 可能会发生异常的代码块
} catch (Exception | Exception2 e) {
// 捕获并处理try抛出的异常类型
} finally {
// 无论是否发生异常,都将执行的代码块
}
复制代码