: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 { // 无论是否发生异常,都将执行的代码块 } 复制代码