Java错误处理、捕获异常、使用异常的方法
异常
如果某个方法不能够采用正常的途径完成它的任务,就可一通过抛出一个封装了错误信息的对象,则这个方法会立即退出,并不返回正常值或任何值。
此外,也不会调用这个方法的代码继续执行,取而代之的是这个异常处理机制开始搜索能够处理这种异常的异常处理器。
在java中,所有异常类都是派生于Throwable类的一个实例。
Erro和Exception都直接继承于Throwable类
Erro:该类层次描述了java运行时系统内部错误和资源耗尽错误,程序中不应抛出该类错误。出现该类错误,除了通知用户,并尽力终止程序。从Erro继承的异常我们不需要抛出,任何代码都有可能抛出那些异常,我们无法控制。
RuntimeException:我们不需要抛出从RuntimeException继承的异常,因为这些异常完全在我们的控制之中
在java程序中应重点关注Exception层次结构。
IOException和RuntimeException派生于Exception
一般规则:由编程错误产生的异常属于RuntimeException,程序本身没问题,但由于想I/O错误这类问题导致的异常属于其他异常
派生于RuntimeException的异常包括以下问题
- 错误的强制类型转换
- 数组访问越界
- 访问null指针
派生于不是RuntimeException的异常包括
- 试图超于文件末尾继续读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找class对象,而这个字符串表示的类不存在。
如果出现RuntimeException异常,则一定是自己代码问题,如因该检测数组下标来避免ArrayIndexOutOfBoundsException;使用变量之前检查它是否为空来避免NullPointerException。
不是RuntimeException的异常为什么不是自己代码问题呢?例如检查一个文件是否存在,可能文件在你检查文件之后,文件就遭到了删除。所以它取决于环境,而不是你的代码。
非检查型异常(不需要抛出)
java中将派生于Erro类或RuntimeException类的所有异常称为非检查型异常。
该类异常要么是在你掌控之中的异常,要么是Erro异常。所以该类异常不需要抛出
检查型异常(需要抛出)
除了非检查型异常外的所有异常称为检查型异常。对于检查型异常一定要声明抛出,如果未抛出异常导致没有处理器捕获这个异常,当前执行的线程就会终止。
如果方法没有声明所有可能发生的检查型异常,编译器会发出一个错误消息。
类继承中的异常
如果父类中的方法抛出了一个异常,子类覆盖该方法的时候不能比父类的异常更通用,即子类只能抛出更具体的异常,或不抛出任何异常。
try/catch语句块
如果try语句块中的代码抛出了catch子句中指定的一个异常类,那么
- 程序将跳过try语句块中的其他代码
- 程序将执行catch中的处理器代码
- 如果try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句。
- 如果try抛出了一个catch中没有的异常,那么这个方法会立即退出(希望它的调用者为这种类型的异常提供了catch子句)。
异常处理原则
通常最好的选择是将异常传递给调用者。
一般经验是捕获那些你知道如何处理的异常,而继续传播那些你不知道怎么处理的异常。
传播异常:在方法首部添加一个throws说明符,提醒调用者这个方法可能会抛出异常。
例外:如果父类方法未抛出异常,而子类覆盖该方法需要抛出异常则必须在子类中捕获该异常。
不允许在子类的throws说明符出现超类方法未列出的异常类
捕获多个异常
try{
code
}catch (xxxException e){
emergency action for missing xxx
}catch (xxxException e){
emergency action for missing xxx
}catch (IOException e){
emergency action for all other I/O problems
}
想要获得异常对象更多的信息:e.getMessage()
得到详细的错误信息(如果有的话):e.getClass().getName()
在java7中同一个catch可以捕获多个异常类型
try{
code
}catch (FirstException | SecException e){
emergency action for missing Firstxxx and Secxxx
}catch (IOException e){
emergency action for all other I/O problems
}
只有First和Sec不存在子类关系时才需要这个特性。
再次抛出异常与异常链
可以在catch中抛出一个异常。
有时候我们并不想知道具体错误细节,只想知道时哪个子系统出现了问题。
例如执行一个servlet代码不需要知道具体错误原因,只希望明确时servlet是否有问题。
try{
code
}catch(SQLException e){
throw new ServletException("database error" + e.getMessage());
}
或者使用更好的方法,把原始异常设置为新的异常的”原因“
try{
code
}catch(SQLException original){
var e = new ServletException("database error");
e.initCause(original);
throw e;
}
捕获这个异常的时候就可以使用下面这条语句获取原始异常
Throwable original = caughtException.getCause();
强烈推荐这种包装技术,这样可以在子系统中抛出高层异常,而不丢失原始异常的细节。
记录异常,再抛出
try{
code
}catch(Exception e){
logger.log(level,msg,e);
throw e;
}
finally子句
代码抛出异常的时候就会终止剩余代码,但是如果这个方法已经获取了一些本地资源,而这些资源必须清理的问题。
解决方案
捕获这个方法产生的所有异常,完成资源清理,再抛出。导致问题:正常代码中需要清理资源,异常中清理资源。这个方法稍显繁琐,此时finally子句就可解决这个问题。
不管是否由异常被捕获,finally子句都会执行。
try子句可以没有catch子句
如果try子句真遇上了异常需要抛出,则必须有catch子句来捕获。
InputStream in = ...;
try{
try{
code
}finally{
in.close();
}
}catch (IOException e){
show error;
}
内层try只负责关闭流,外层try负责捕获异常。
该解决发难功能明确,功能强,推荐使用。
public class TryFinallyTest {
public static int parseInt(String n) {
try {
return Integer.parseInt(n);
}finally {
return 0;
}
}
public static void main(String[] args) {
System.out.println(TryFinallyTest.parseInt("not a number"));
}
}
it will print 0
finally子句总是最后执行,它甚至会吞掉异常。
注意:finally子句要用于清理资源,不要把改变控制流的语句(return,throw,break)放在finally中。
try-with-Resources
假设资源实现了一个AutoCloseable接口
该接口方法 void close() throws Exception
Closeable接口时AutoCloseable子接口,其close()抛出IOException异常
try(Resource res = ...){
work with res
}
try 正常退出或异常退出时,会自动调用res.close()