11.1.1 异常分类
如果一个程序出现了RuntimeException,那么就一定是你的问题。
11.1.2 声明已检测异常
如果子类覆盖了父类的一个方法,那么子类方法中声明的检查异常不能比超类方法中声明的异常更通用。
11.1.4 创建异常类
所有自定义的异常类,都应该实现两个构造函数,一个是默认构造函数;一个是带string参数的构造函数;
11.2 捕获异常
通常来说应该捕获那些知道如何处理的异常,而将那些不知道如何处理的异常继续进行传递。
11.2.1 捕获多个异常
在java7中,同一个catch语句可以捕获多个异常,比如:
try{
....
}catch(FileNotFoundException | UnknownHostException){
...
}
只有当捕获的异常类彼此之间不存在父子关系的时候才需要这种方法。
11.2.2 再次抛出异常或者异常链
将原始异常设置为新异常的原因。
try{
...
}catch(Exception e){
Throwable se = new ServetException("database error: ");
se.initCause(e);
throw se;
}
当捕获异常时,就可以使用Throwable se = e.getCause();来得到原始异常。
11.2.4 带资源的try语句
一看语法就知道,这个和c#中的using语句是一样的
try(Resources res = ){
work with this res
}
try块退出时,会自动调用res.close()语句。(在c#中是调用res.dispose()方法)
比如下面的两端代码是等价的
FileInputStream fs=null;
try{
fs = new FileInputStream("D:\test.txt");
fs.xxxxx
}
finally{
if(fs != null) fs.close();
}
try(FileInputStream fs= new FileInputStream("D:\test.txt")){
fs.xxxx
}
显然,第二段代码简洁很多。
而且在try块中可以定义多个资源,逗号分隔,然后编译器会自动调用所有资源的close()方法。
11.2.5 分析堆栈跟踪信息
Throwable t = new Throwable();
StackTraceElement[] frames = t.getStackTrace();
for(StackTraceElement frame : frames){
//xxxxx
}
通过StackTraceElement可以获取到对应的文件名称、错误的行号、类名、方法名等信息,这样可以更加方便的分析和调试一问题。
下面的方法可以获取当前应用程序的所有线程的堆栈信息,这个在以前用c#的时候没有看到过,这也从侧面看出多学习不同语言的重要性。
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTrace();
for(Thread t: map.keys()){
StackTraceElement frames = map.get(t);
analyze frame
}
StackTraceElement 的几个典型方法
String getFileName();
int getLineNumber();
String getClassName();
String getMethodName();
boolean isNativeMethod();
String toString();
11.3 使用异常的技巧
1)异常处理不能代替简单的测试,也就是不要用异常来做程序判断的分支;
2)要适当的利用异常层次结构,抛出或者捕获的时候,要尽量使用能够表达特定目的的类,这样会具有更好的可读性,不要任何时候都使用Throwable,那样可读性很差的;
3)在检测错误的时候,“苛刻”比放任更好;比如Stack.pop()是返回null更好还是跑出EmptyStackException更好?作者认为后者更好。
4)不要羞于传递异常。如果调用了一个跑出异常的方法,例如FileInputSteam构造器或者readLine方法,这些方法就会本能地捕获这些可能产生的异常。其实传递异常比捕获异常更好。
public void readStuff(Sting fileName) throw s IOException{
InputStream is = new FileInputStream(fileName);
//xxxx
}
让高层次的用户知道发生了错误,或者放弃不成功的命令更加合适。
那么以上两点就可以归纳为:早抛出,晚捕获。