1、所有的异常都是由Throwable继承而来,但在下一层立即分解为两个分支:Error和Exception。
Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。
Exception类层次结构又分为两个分支:一个分支派生于RuntimeException;另一个分支包含其他异常。
划分两个分支的规则是: 由程序错误导致的异常属于RuntimeException;而程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常。
派生于RuntimeException的异常包含下面几种情况:
1> 错误的类型转换
2> 数组访问越界
3> 访问null指针
不是派生于RuntimeException的异常包括:
1> 试图在文件尾部后面读取数据
2> 试图打开一个不存在的文件
3> 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在
Java语言规范将派生于Error类或RuntimeException类的所有异常称为非受查(unchecked)异常,所有其他的异常称为受查(checked)异常。
创建异常类
如 :定义一个派生于IOException的类,定义的类应该包含两个构造器,一个是默认的构造器;另一个是带着详细描述信息的构造器
class FileFormatException extends IOException {
public FileFormatException() {}
public FileFormatException(String gripe) {
super(gripe);
}
}
捕获异常
要想捕获一个异常,必须设置try/catch语句块
public void read(String filename) {
try {
InputStream in = new FileInputStream(filename);
int b;
while ((b = in.read()) != -1) {
process input
}
} catch (IOException exception) {
exception.printStackTrace();
}
}
read方法有可能抛出一个IOException异常。在这种情况下,将跳出整个while循环,
进入catch子句,并生成一个栈轨迹。对于一个普通的程序来说,这样处理异常基本上合乎情理。
还有其他选择吗?
通常,最好的选择是什么也不做,而是将异常传递给调用者。如果read方法出现了错误,
就让read方法的调用者去操心。如果采用这种处理方式,就必须声明这个方法可能会抛出一个
IOException。
public void read(String filename) throws IOException {
InputStream in = new FileInputStream(filename);
int b;
while ((b = in.read()) != -1) {
process input
}
}
如果调用了一个抛出受查异常的方法,就必须对它进行处理,或继续传递。
通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续进行传递。
如果想传递一个异常,就必须在方法的首部添加一个throws说明符,以便告知调用者这个方法可能会抛出异常。
再次抛出异常与异常链
在catch子句中可以抛出一个异常,这样做的目的是改变异常的类型。
捕获异常并将它再次抛出的基本方法:
try {
access the database
} catch (SQLException e) {
throw new ServletException("database error : " + e.getMessage());
}
一种更好的异常处理方式,并且将原始异常设置为新异常的“原因”:
try {
access the database
} catch (SQLException e) {
Throwable se = new ServletException("database error");
se.initCause(e);
throw se;
}
当捕获到异常时,就可以使用下面这条语句重新得到原始异常:
Throwable e = se.getCause();
有时可能只想记录一个异常,再将它重新抛出,而不做任何改变:
try {
access the database
} catch (Exception e) {
logger.log(level, message, e);
throw e;
}
finally 子句
不管是否异常被捕获,finally子句中的代码都被执行。
InputStream in = new FileInputStream(...);
try {
// 1
code that might throw exceptions
//2
} catch (IOException e) {
//3
show error message
//4
} finally {
//5
in.close();
}
// 6
有下列三种情况会执行finally子句:
1> 代码没有抛出异常。在这种情况下,程序首先执行try语句块中的全部代码,然后执行finally
子句中的代码。随后,继续执行try语句块之后的第一条语句。也就是说执行标注1、2、5、6处。
2> 抛出一个在catch子句中捕获的异常。在这种情况下,程序将执行try语句块中的所有代码,直到发送异常为止。此时,将跳过try语句块中的剩余代码
,转去执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。
如果catch子句没有抛出异常,程序将执行try语句块之后的第一条语句。在这里
执行标注1、3、4、5、6处的语句
3> 代码抛出了一个异常,但这个异常不是由catch子句捕获的。在这种情况下,程序将执行try语句
块中的所有语句,直到有异常被抛出为止。此时,将跳过try语句块中的剩余代码,然后执行finally
子句中的语句,并将异常抛给这个方法的调用者。执行标注1、5处的语句。
当finally子句包含return语句时,将会出现一种意想不到的结果。假设利用return语句从try语句块中退出。在方法返回前,finally子句的内容将被执行。如果finally子句中也有一个return语句,这个返回值将会覆盖原始的返回值。
public static int f(int n) {
try {
int r = n * n;
return r;
} finally {
if (n == 2) return 0;
}
}
分析堆栈轨迹元素
堆栈轨迹(stack trace)是一个方法调用过程的列表,它包含了程序执行过程中方法调用的
特定位置。
可以调用Throwable类的printStackTrace方法访问堆栈轨迹的文本描述信息。
当Java程序正常终止,而没有捕获异常时,这个列表就会显示出来。
Throwable t = new Throwable();
StringWriter out = new StringWriter();
t.printStackTrace(new PrintWriter(out));
String description = out.toString();
一种更灵活的方法是使用getStackTrace方法,它会得到StackTraceElement对象的一个数组。如:
Throwable t = new Throwable();
StackTraceElement[] frames = t.getStackTrace();
for (StackTraceElement frame : frames)
analyze frame
静态的Thread.getAllStackTrace方法,可以产生所有线程的堆栈轨迹。
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
for (Thread t : map.keySet()) {
StackTraceElement[] frames = map.get(t);
analyze frames
}
记录日志
可以很容易地取消全部日志记录,或者仅仅取消某个级别的日志,而且打开或关闭这个操作很容易。
可以很简单地禁止日志记录的输出,因此,将这些日志代码留在程序中的开销很小。
日志记录可以被定向到不同的处理器,用于在控制台中显示,用于存储在文件中等。
日志记录可以采用不同的方式格式化,如:纯文本 XML
在默认情况下,日志系统的配置由配置文件控制。
可以调用getLogger方法创建或获取记录器:
private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp");
未被任何变量引用的日志记录器可能会被垃圾回收。为了防止这种情况发生,要像上面一样,用一个静态变量存储日志记录器的一个引用。
日志记录器的父与子之间将共享某些属性。
有以下7个日志记录器级别:
1> SEVERE
2> WARNING
3> INFO
4> CONFIG
5> FINE
6> FINER
7> FINEST