在Java中,异常对象都是派生于Throwable类的一个实例。
派生于RuntimeException类或者Error类的所有异常称为【未检查异常】,其他的异常称为【已检查异常】。
Error类描述Java运行时系统的内部错误和资源耗尽错误;
派生于RuntimeException的异常包括:错误的类型转换;数组越界访问;访问空指针。
不是派生于RuntimeException的异常包括:试图在文件尾部后面读取数据;试图打开一个格式错误的URL;试图根据给定的字符串查找Class对象,而这个字符串表示的类不存在。
在自己编写方法时,需要记住遇到下面4中情况时才会抛出异常:
1)调用一个抛出已检查异常的方法;
2)在程序运行过程中发现错误,并且利用throw语句抛出一个已检查异常;
3)程序出现错误;
4)Java虚拟机和运行时库出现的内部异常。
如果一个方法有可能抛出多个已检查异常,就必须在方法的首部列出所有的异常类。每个异常类之间用逗号隔开。
不需要声明从Error继承的那些异常,任何程序代码都有抛出这类异常的能力,而我们对他们没有任何控制能力。
同样,也不应该声明从RuntimeException继承的那些未检查异常。这些运行时错误完全在我们的控制之下,应该多花时间修正程序的错误,而不是说明这种错误发生的可能性。
如果在子类中覆盖了超类的一个方法,那么子类方法中声明的已检查异常不能超过超类方法中声明的异常范围。特别是,如果超类没有抛出任何已检查异常,那么,子类也不能抛出任何已检查异常。
假设一个名为readData的方法正在读取一个首部具有下列信息的文件:Content-length:1024,然而,读到733个字符之后文件就结束了,此时,我们认为这是一种非正常的情况,希望抛出一个异常。
查看Java API文档后发现:EOFException异常的描述是:在输入过程中,遇到一个未预期的EOF后的信号。这正是我们要抛出的异常。
String readData(Scanner in) throws EOFException { ... while(...) { if(!in.hasNext()) //EOF encountered { if(n<len) throw new EOFException(); } ... } return s; }
综上,抛出一个已经存在的异常类是很容易的:
1)找到一个合适的异常类;
2)创建这个类的一个对象;
3)将对象抛出。
有时候,找不到合适的已存在异常类,就需要我们自己创建异常类了。我们要做的是定义一个派生于Exception的类,或者派生于Exception子类的类。习惯上,定义的类应该包括两个构造器:一个是默认的构造器,一个是带有详细描述信息的构造器。
class FileFormatException extends IOException { public FileFormatException(){} public FileFormatException(String gripe) { super(gripe); } } //现在,就可以抛出自己定义的异常类型了 String readData(BufferedReader in) throws FileFormatException { ... while(...) { if(ch == -1) //EOF encountered { if(n < len) throw new FileFormatException(); } ... } return s; }
捕获异常
要想捕获一个异常,必须设置try/catch语句块。try语句块的最简单形式如下:
try { code more code more code } catch(ExceptionType e) { handler for this type }
如果try语句块中的任何代码抛出了一个在catch子句中指定的异常类,那么
1)程序将跳过try语句块中的其余代码。
2)程序将执行catch子句中的处理器代码。
如果在try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句。
如果方法中的任何代码抛出了一个在catch子句中没有声明的异常类型,那么这个方法将立刻退出。
如果调用一个抛出已检查异常的方法,就必须对它进行处理(try/catch),或者将它传递出去(throw)。
捕获多个异常
在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做不同的处理。可以像下面这样为每个异常类型使用一个单独的catch子句:
try { code that might throw exceptions } catch(MalformedURLException e1) { emergency action for malformed URLs } catch(UnknownHostException e2) { emergency action for unknown hosts } catch(IOException e3) { emergency action for all other I/O problems }
可以再catch子句中抛出异常
finally子句
Graphics g = image.getGraphics(); try { //1 code that might throw exceptions //2 } catch(IOException e) { //3 show error dialog //4 } finally { //5 g.dispose(); } //6
无论是否抛出异常,finally子句的代码都会被执行。如果抛出异常,finally在catch之后执行。
try可以只跟finally,而不必catch。强烈建议独立使用try/catch和try/finally语句块,提高代码清晰度。
InputStream in = ...; try { try { code that might throw exceptions } finally { in.close(); } } catch(IOException e) { show error dialog }
在内层的try语句块只有一个职责,就是确保关闭输入流。在外层的try语句块也只有一个职责,就是保证报告出现的错误。这种解决方案不仅清楚,而且还具有一个功能,就是报告finally子句中出现的错误。
堆栈跟踪元素分析
调用Throwable类的getStackTrace方法获得一个StackTraceElement对象的数组,可以在程序中分析这个对象。例如:
Throwable t = new Throwable(); StackTraceElement[] frames = t.getStackTrace(); for(StackTraceElement frame:frames) analyze frame
StackTraceElement类含有获取文件名和当前执行的代码行号的方法。同时,还含有获取类名和方法名的方法。toString方法将产生一个格式化的字符串,其中包括所获取的信息。
在JDK5.0中,增加了静态的Tread.getAllStackTrace方法,它可以产生所有线程的堆栈跟踪:
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces(); for(Thread t:map.keySet()) { StackTraceElement[] frames = map.get(t); analyze framse }
调试技术
如果完全手工地进行调试,下面的一些建议有益于提高调试效率。
1)可以使用下面的方法打印或者记录任意变量的值:
System.out.println("x="+x);
或者Logger.global.info("x="+x);
如果x是一个数值,就会转换成等价的字符串。如果x是一个对象,就会调用toString方法。在自定义类中,也应该定义自己的toString方法(覆盖Object类的此方法)。
2)在每一个类中放置一个main方法,这样就可以对每一个类进行单元测试。
3)日志代理
4)利用Throwable类提供的printStackTrace方法,获取堆栈跟踪。
JDB调试器
-g选项编译程序
启动:jdb 程序名
过程:设置断点->运行->查看局部变量
设置断点:stop in class.method 或 stop at class:line
运行:run
查看位置:list
查看所有局部变量:locals
下一行:next
进入函数:step
退出:quit