一、Exception
当程序违反Java 编程语言的语义约束时,Java 虚拟机将此错误作为异常发送给程序。
举个违规的例子,试图在数组的边界之外建立索引。一些编程语言及其实现通过强制终止程序来应对这些错误;其他编程语言允许实现以任意或不可预测的方式进行响应。这两种处理方式都不符合Java SE 平台的设计目标:提供可移植性和健壮性。相反,当违反语义约束,Java 编程语言指定会抛出一个异常,并且将从异常点导致非本地的控制转移发生,这个异常点能够由程序员指定。
一个异常一边是在发生时,从异常点抛出(thrown);一边是在控制被转移到的地方被捕获(caught)。
try{
//发生时, 异常点
}catch(Exception e){
// 控制被转移到的地方
}
程序也能够通过抛出(throw)语句显示地抛出异常。
显式使用throw语句提供了一种处理错误条件的替代方法,这种方法通过返回有趣的值来处理错误条件,例如通常不希望出现负值的整数值-1。经验表明,调用者常常忽略或不检查这些有趣的值,从而导致程序不够健壮、表现出不受欢迎的行为,或者两者兼有。
每一个异常是由类 Throwable 的实例或者它的一个子类来代表的。这样的对象能够被用来从异常点携带信息,并在捕获的时候处理它。
处理程序通常被建立是在 try 语句的catch 从句。
try{
//异常点,携带信息
}catch(Exception e){
// 处理 e
}
在抛出异常的过程中,Java虚拟机突然一个接一个地完成在当前线程中已经开始但未完成执行的任何表达式、语句、方法和构造函数调用、初始化器和字段初始化表达式。这一过程持续进行直到处理程序发现表明它处理地特定异常被命名的异常类或异常的类的父类(
- 为什么会是父类?我想这里应该是指在catch 的时候,可以直接catch 异常点抛出的异常类的父类。
)。如果没有找到这样的处理程序,那么异常可能是由一个未捕获的异常处理程序的层次结构。
将Java SE平台的异常机制与其同步模型集成,这样,当同步语句和同步方法的调用突然完成时,监视器将被解锁。
二、The Kinds and Causes of Exceptions
一个异常是由类 Throwable(Object 的直接子类) 的实例或者它的一个子类来代表的。
Throwable 和它所有的子类 统称为异常类。
类 Exception 和 Error 是 Throwable 的直接子类:
- Exception是普通程序希望能够从中恢复的所有异常的超类。
- RuntimeException类是Exception的一个直接子类。RuntimeException是所有可能在表达式求值期间由于许多原因而抛出,但是仍然可以从中恢复的异常的超类。
- RuntimeException及其所有子类统称为运行时异常类。
- Error是所有普通程序通常不会从中恢复的异常的超类。
Error及其所有子类统称为Error类
未受检(unchecked的异常类是运行时异常类和错误类。受检(checked) 异常类是除了未受检异常类的所有异常类。
程序可以在抛出语句中使用Java SE平台API的预先存在的异常类,或者根据需要将其他异常类定义为可抛出的或其任何子类的子类。利用编译时检查异常处理程序, 通常将大多数新异常类定义为已检查的异常类,即是Exception的子类,而不是RuntimeException的子类。
Error 是 Throwable 单独的子类,不同于Exception 的类层次结构,允许使用 “} catch (Exception e) {”来捕获所有可能恢复的异常除了捕获 errors,即恢复通常是不可能的。
引发异常的原因通常有三个:
-
throw 语句被执行
-
Java 虚拟机同步检测到异常执行情况,即:
-
一个表达式违背了正常的Java 编程语言的语义,如一个整数除以零。
- 在加载时出现错误,链接,或初始化程序的一部分; 在这种情况下,会抛出LinkageError子类的一个实例
- 内部错误或资源限制阻止Java虚拟机实现Java编程语言的语义; 在这种情况下,会抛出VirtualMachineError子类的一个实例
这些异常不是在程序中的任意点上抛出的,而是在表达式求值或语句执行的可能结果中指定的点上抛出的。
-
异步异常发生
大多数异常是同步发生的,它们发生是由它们的线程的操作以及程序中指定可能导致此类异常的点所导致的。相反,异步异常则是在程序执行过程中的任何时刻都可能发生的异常。
异步异常发生在以下情况下: -
调用类Thread或ThreadGroup的(已弃用的)stop方法。
一个线程可以调用(已弃用)stop方法来影响另一个线程或指定线程组中的所有线程。它们是异步的,因为它们可能发生在其他或多个线程的执行过程中的任何一点 -
Java虚拟机中的内部错误或资源限制,使其无法实现Java编程语言的语义。在这种情况下,抛出的异步异常是VirtualMachineError子类的一个实例
StackOverflowError是VirtualMachineError的子类,它可以通过方法调用同步抛出,也可以由于本机方法执行或Java虚拟机资源限制而异步抛出;类似地,在类实例创建、数组创建、类初始化和装箱转换过程中,可以同步抛出VirtualMachineError的另一个子类OutOfMemoryError,也可以异步抛出。
Java SE平台允许在抛出异步异常之前执行少量但有限制的执行(
-
我也没有get到这句话的意思
)。
异步异常很少,但是如果要生成高质量的机器码,就必须正确理解它们的语义。
编译时异常检测
Java编程语言要求程序包含用于检查异常的处理程序,这些异常可能是由于方法或构造函数的执行而导致的。在编译时检查异常处理程序是否存在,目的是减少未正确处理的异常数量。对于可能出现的每个检查异常,方法或构造函数的throw子句必须提到该异常的类或该异常的类的一个超类。
在throw子句中命名的受检异常类是方法或构造函数的实现者和用户之间的契约的一部分。覆盖方法的throw子句不能指定此方法将导致抛出,其throw子句不允许覆盖方法抛出的任何已检查异常。当涉及到接口时,单个覆盖声明可以覆盖多个方法声明。在这种情况下,覆盖声明必须具有与所有覆盖声明兼容的throw子句。
未受检的异常类将免除编译时异常检测。
错误类可以被豁免,因为它们可以发生在程序中的许多点上,并且很难或不可能从它们中恢复。一个声明此类异常的程序将是混乱的,毫无意义的。复杂的程序可能希望捕获并尝试从这些条件中恢复。
Java编程语言的设计者认为,必须声明这样的异常对于建立程序的正确性并没有太大的帮助,因此不允许使用运行时异常类。Java编程语言的许多操作和构造都可能在运行时导致异常。Java编译器可用的信息以及编译器执行的分析级别通常不足以确定此类运行时异常不会发生,即使这对程序员来说可能是显而易见的。要求声明这样的异常类只会让程序员感到恼火。
例如,某些代码可能实现一个循环数据结构,通过构造,该结构永远不会涉及空引用;程序员可以确定NullPointerException不会发生,但是Java编译器很难证明这一点。建立这种数据结构的全局属性所需的理论证明技术超出了本规范的范围。
Catching Checked Exceptions 的例子:
import java.io.*;
class StaticallyThrownExceptionsIncludeSubtypes {
public static void main(String[] args) {
try {
throw new FileNotFoundException();
} catch (IOException ioe) {
// 捕获IOException 及其任何子类型
}
try {
throw new FileNotFoundException();
// Statement "can throw" FileNotFoundException.
// It is not the case that statement "can throw"
// a subtype or supertype of FileNotFoundException.
} catch (FileNotFoundException fnfe) {
// ... Handle exception ...
} catch (IOException ioe) {
//合法,但Java SE 7 编译器会提供警告,因为所有的IOException 子类型 try 块 已经被前一个catch 给捕获了。
}
try {
// m 的方法声明 “throws IOException”
m();
} catch (FileNotFoundException fnfe) {
// 合法,因为动态类型的异常可能是FileNotFoundException
} catch (IOException ioe) {
// Legal, because the dynamic type of the exception
// might be a different subtype of IOException. 我觉得这里的说法可能有点问题,因为当前catch的并不是IOException的一个子类,但我们应该明白,它想表达的是我们依然能够catch IOException的其它子类(因为动态类型)。
} catch (Throwable t) {
// Can always catch Throwable.
}
}
static void m() throws IOException {
throw new FileNotFoundException();
}
}
四、异常的运行时处理
如果找不到可以处理异常的catch子句,则终止当前线程(遇到异常的线程)。在终止之前,执行所有finally子句,并按照以下规则处理未捕获的异常:
- 如果当前线程有未捕获的异常处理程序集,则执行该处理程序。
- 否则,将为当前线程的父线程组ThreadGroup调用uncaughtException方法。如果ThreadGroup及其父线程组没有覆盖uncaughtException,则调用默认处理程序的uncaughtException方法。
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread thread, Throwable thrown) {
.....
}
}
// 只针对当前线程有效
Thread.currentThread().setUncaughtExceptionHandler((Thread t, Throwable e)->{});