文章目录
Java异常
异常体系
Throwable --| Error --| VirtualMacthineError
--| ThreadDeath
--| ...
--| Exception --| RuntimeException
--| ...
Error
其 实例以及其子类的实例,都表示JVM运行错误,不能通过代码进行处理
VirtualMacthineError
虚拟机错误
ThreadDeath
线程死锁
Exception
其 实例以及其子类的实例,表示在程序运行过程中,不期望出现的异常,可以被JVM的异常处理机制处理。
分类
非检查异常
unchecked exception
Error
和 RuntimeException
及其子类
例:
ArithmeticException
计算条件异常,比如 除0
ClassCastException
强制类型转换异常
ArrayIndexOutOfBoundsException
数组索引越界异常
NullPointerException
NPE
最容易出现的空指针异常
检查异常
checked exception
除Error
和 RuntimeException
之外的其他异常
非检查异常和检查异常的区别
对于非检查异常,javac在编译期间不会发现并提示该异常,且并不要求在程序中处理该异常,但仍可以在代码中使用try catch
语句进行捕获和处理。但此类异常属于代码缺陷,最好不要进行异常捕获,而应该进行代码修正。
对于检查异常,javac会强制要求代码开发者对此类异常进行处理,要么捕获要么抛出,否则编译无法通过。
异常追踪栈
示例代码运行结果如下
lower running ...
com.example.exception.HigherException: HigherException message & LowerException message
at com.example.exception.TestDemo.higherRun(TestDemo.java:29)
at com.example.exception.TestDemo.run(TestDemo.java:20)
at com.example.exception.TestDemo.main(TestDemo.java:13)
Caused by: com.example.exception.LowerException: LowerException message
at com.example.exception.TestDemo.lowerRun(TestDemo.java:36)
at com.example.exception.TestDemo.higherRun(TestDemo.java:26)
... 2 more
注:Caused by 后输出信息为异常链的内容,具体将在 异常链 章节中进行具体分析,在本章节中不进行分析
根据示例代码,在方法higherRun()
中最先抛出异常,导致所有直接调用或者间接调用该方法的方法都受到影响。当这些方法输出异常信息时,就形成了异常追踪栈
分析
com.example.exception.HigherException: HigherException message & LowerException message
at com.example.exception.TestDemo.higherRun(TestDemo.java:29)
at com.example.exception.TestDemo.run(TestDemo.java:20)
at com.example.exception.TestDemo.main(TestDemo.java:13)
方法higherRun
抛出异常,调用该方法的 run
方法无法继续执行,也抛出异常,之后调用run
方法的main
方法也抛出异常,在栈中就是从栈顶向栈底回溯。由于最终异常没有进行处理,直接抛给了JVM,故程序终止。
按顺序自上而下,第一行信息即为最早抛出的异常点。根据该异常点的类名、方法名以及异常代码所在行,即可定位到出现异常的代码位置。
异常信息输出格式
第一行:异常类型
:
异常原因的说明文字
其余行:at
包路径
.
类名
.
方法名
(
文件名
:
异常代码所在行
)
异常处理
异常捕获
关键字
try
catch
finally
使用方式
try {
...
} catch (LowerException e) {
...
} catch (Exception e) {
...
} finally {
...
}
特点
try
中为会抛出异常的代码。catch
提供可匹配的异常类和对应的处理代码。finally
可选,若存在则必定执行,多用来处理流的关闭等清理工作。
执行顺序
try
捕获异常,将捕获的异常与catch
匹配,匹配到则执行该catch
中的代码,没有匹配到则继续执行try...catch...
后的代码,如果有finally
,则先执行该块中代码,再执行后面的代码。
注意事项
- 自
JDK7
,可以将处理方式相同异常以try { ... } catch (AException | BException | ... e) { ... }
的形式进行捕获,多个异常类必须相互独立,不可存在继承关系,否则无法通过编译。 - 在
try
中捕获的异常按顺序与catch
匹配,匹配到执行相应的catch
代码块,并且不再与后面的catch
匹配,与所有catch
未匹配到,异常将会抛给上层调用者。 - 由于
catch
的异常类可以匹配到该异常类以及该异常类的子类,故如若多个catch
的异常类存在继承关系,则需要将父异常类放置在子异常类的后面,否则将永远无法匹配到子异常类,也不能通过编译。 try
catch
finally
中的局部变量相互独立,不能共享使用。catch
可不存在,若catch
不存在则finally
必须存在,且无catch
的情况下,异常无法处理。finally
–try
执行则finally
必定执行,除非使用System.exit()
。
– 若finally
中存在return
,则try
或者catch
中的return
的值会被finally
中return
的值覆盖
– 若finally
中存在return
,则try
或者catch
中即使抛出异常,该异常也不会再上抛,而是正常返回。原因应该是finally
中存在return
时,该方法的退出方法由异常完成出口更改为了正常完成出口,异常不再上抛,而是正常return
;(是否是该原因存疑,需要验证;另外,退出方法、异常完成出口、正常完成出口的解析,请查阅 JVM栈帧 的相关知识)
– 若finally
中存在异常,则try
或者catch
中的上抛的异常将会被finally
中上抛的异常覆盖
异常上抛
Throws
将异常上抛至上层调用者
若在方法中抛出检查异常checked exception
, 而该方法并没有处理该异常,就必须使用 throws
将异常上抛,否则无法通过编译。非检查异常unchecked exception
也可以上抛,但无意义。
异常链
在同模块或者不同模块之间的方法调用中,容易出现被调用方法抛出的异常被调用方法抛出的异常掩盖掉的问题,这也导致真正的异常源信息丢失。
比如,现有两个方法,一个除法运算方法A
,一个数字输入方法B
。A
调用B
获取数字进行运算,如果B
出现异常BException
(以下简称BE
),导致A
无法完成运算而出现异常AException
(以下简称AE
),对于A
的上层调用者C
来说,C
接收到的应该是AE
,而不是BE
。但BE
又不能丢失,故将BE
封装到AE
中,形成异常链,对于C
来说,C
接受的仍然是AE
,在异常定位时,又可以找得到BE
这个异常源。
在Throwable
中的源码中,可以看到一个私有属性 cause
,类型为Throwable
,就是通过这个属性封装下层源异常类
/**
* Throwable 部分源码
*/
public class Throwable implements Serializable {
...
private Throwable cause = this;
public Throwable(Throwable cause) {
fillInStackTrace();
detailMessage = (cause==null ? null : cause.toString());
this.cause = cause;
}
public Throwable(String message, Throwable cause) {
fillInStackTrace();
detailMessage = message;
this.cause = cause;
}
}
自定义异常
自定义检查性异常 继承Exception
自定义运行时异常 继承RuntimeException
按照国际惯例,自定义的异常应该总是包含如下的构造函数:
- 一个无参构造函数
- 一个带有
String
参数的构造函数,并传递给父类的构造函数。 - 一个带有
String
参数和Throwable
参数,并都传递给父类构造函数 - 一个带有
Throwable
参数的构造函数,并传递给父类的构造函数。
例如:
public class IOException extends Exception {
static final long serialVersionUID = 7818375828146090155L;
public IOException()
{
super();
}
public IOException(String message)
{
super(message);
}
public IOException(String message, Throwable cause)
{
super(message, cause);
}
public IOException(Throwable cause)
{
super(cause);
}
}
多线程与异常处理
异常处理器
UncaugthExceptionHandler
使用举例
public class TestThread extends Thread {
public void run() {
throw new NullPointerException("空指针异常");
}
}
public class TestThreadDemo {
public static void main(String[] args) {
TestThread t1 = new TestThread();
t1.setName("线程1");
setUncaughtExceptionHandler(t1);
t1.start();
TestThread t2 = new TestThread();
t1.setName("线程2");
setUncaughtExceptionHandler(t2);
t2.start();
}
private static void setUncaughtExceptionHandler(TestThread testThread) {
testThread.setUncaughtExceptionHandler((t, e) -> {
System.out.println(" 线程:" + t.getName() + " 出现了异常:");
e.printStackTrace();
});
}
}
执行结果(日志打印顺序不一定如下图所示)
线程:线程2 出现了异常:
java.lang.NullPointerException: 空指针异常
at com.example.exception.TestThread.run(TestThread.java:11)
线程:Thread-1 出现了异常:
java.lang.NullPointerException: 空指针异常
at com.example.exception.TestThread.run(TestThread.java:11)
DefaultUncaugthExceptionHandler
使用举例
public class TestThread extends Thread {
public void run() {
throw new NullPointerException("空指针异常");
}
}
public class TestThreadDemo {
public static void main(String[] args) {
TestThread.setDefaultUncaughtExceptionHandler((t, e) -> {
System.out.println(" 线程:" + t.getName() + " 出现了异常:");
e.printStackTrace();
});
TestThread t1 = new TestThread();
t1.setName("线程1");
t1.start();
TestThread t2 = new TestThread();
t2.setName("线程2");
t2.start();
}
}
执行结果(日志打印顺序不一定如下图所示)
线程:线程2 出现了异常:
java.lang.NullPointerException: 空指针异常
at com.example.exception.TestThread.run(TestThread.java:11)
线程:线程1 出现了异常:
java.lang.NullPointerException: 空指针异常
at com.example.exception.TestThread.run(TestThread.java:11)
Future
如下,使用ExecutorService
创建一个线程池并submit
了一个实现了Callable
接口的线程对象,从而获得封装了该线程执行结果的Future
对象,而如果子线程中发生了异常,通过future.get()
获取返回值时,可以捕获到ExecutionException
异常。
public class ChildrenThread implements Callable<String> {
@Override
public String call() throws Exception {
exceptionMethod();
return "result";
}
private void exceptionMethod() {
throw new RuntimeException("ChildThread exception");
}
}
public class ChildrenThreadDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(8);
Future<String> future = executorService.submit(new ChildrenThread());
try {
future.get();
} catch (InterruptedException e) {
System.out.println(String.format("handle exception in child thread. %s", e));
} catch (ExecutionException e) {
System.out.println(String.format("handle exception in child thread. %s", e));
} finally {
if (executorService != null) {
executorService.shutdown();
}
}
}
}
执行结果
handle exception in child thread. java.util.concurrent.ExecutionException: java.lang.RuntimeException: ChildThread exception
异常处理的一般原则
异常与事务
示例代码
//HigherException
public class HigherException extends Exception {
public HigherException(String message, Throwable cause) {
super(message, cause);
}
public HigherException(String message) {
super(message);
}
}
//LowerException
public class LowerException extends Exception {
public LowerException(String message, Throwable cause) {
super(message, cause);
}
public LowerException(String message) {
super(message);
}
}
//TestDemo
public class TestDemo {
public static void main(String[] args) {
try {
work();
} catch (HigherException e) {
e.printStackTrace();
}
}
public static void work() throws HigherException {
try {
lowerRun();
System.out.println("higher running ...");
} catch (LowerException e) {
throw new HigherException("HigherException message & " + e.getMessage(), e);
}
}
public static void lowerRun() throws LowerException {
System.out.println("lower running ...");
throw new LowerException("LowerException message");
}
}