异常
在Java对象中,异常对象都是派生于Throwable
类的一个类实例
Throwable
|-- Error
|-- Exception
|-- IOException
|-- RuntimeException //由编程错误导致的异常
由编程错误导致的异常属于RuntimeException
;如果程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常。
Java语言规范将派生于Error
类或RuntimeException
类的所以异常称为非检查型异常(unchecked),即其他异常称为检查型异常。
如果在子类中覆盖了超类的一个方法,子类方法中声明的检查型异常不能比超类方法中声明的异常更通用(子类方法可抛出更特定的异常,或者根本不抛出任何异常)。
如果超类方法没有抛出任何检查型异常,子类也不能抛出任何检查型异常。
即是:如果编写一个方法覆盖超类,而这个超类方法没有抛出异常,那就必须捕获子类方法代码中的所有检查型异常。
一旦方法抛出了异常,这个方法就不会返回到调用者。
// 自定义异常
class MyException extends IOException{
public MyExcetion(){}
public MyException(String gripe){
super(gripe);
}
}
自定义的类中应该包含两个构造器,一个是默认的构造器,另一个是包含详细信息的构造器。
再次抛出异常时包装
可以在catch子句中抛出一个异常(通常,希望改变异常的类型时会这样做)
try{
... ...
} catch (SQLException e){
throw new ServletException("database error: " + e.getMessage() );
}
不过,有一中更好的处理方法:把原始异常设置为新异常的原因
try{
...
} catch (SQLException original){
var e = new ServletException("database error");
e.initCause(original);
throw e;
}
// 获取异常、
Throwable original = caughtException.getCause();
建议使用该种包装技术。这样可以在子系统中抛出高层异常,而不会丢失原始异常的细节。、
finally-return
==警告:==当finally
子句包含return
语句时,finally块中的return返回值会屏蔽掉整个try的返回值。
public static int parseInt(String s){
try {
return Integer.passeInt(s);
} finally {
return 0; // ERROR!!!
}
}
- 看起来会在
parseInt("42")
调用中,try块的整体会返回整数42。不过,这个方法真正返回之前,会执行finally
子句,使得方法最终返回0,而忽略之前的原返回值。 - 更糟糕的是,考虑调用
parseInt("zero")
时,Integer.parseInt() 方法会抛出NumberFormatException 异常。然后执行finally
子句,return语句甚至会吞掉整个异常! - *finally子句的体要用于清理资源。*不要将改变控制流的语句(return, throw, break, continue)放在finally子句中。
try-with-Resources
在Java 7中,常规关闭资源方式
// open a resource
try{
// work with resource
} finally {
// close the resource
}
使用try-with-resources
语句:
try(Resource res = ... ){
// work with res
}
当 try 代码块(正常或异常)退出时,会自动调用 res.close()
。
/**
* 指定多个资源
*/
try (var in = new Scanner(new FileInputStream("in.txt"), StandardCharsets.UTF_8);
var out = new PrintWriter("out.txt", StandardCharsets.UTF_8))
{
// work with resources
}
// 在Java 9 中,可以在try首部中提供之前声明的实事最终变量
public static printAll(String[] lines, PrintWirter out){
try (out){ // efectively final variable
... ...
}
}
try-with-resources
自身也可以有 catch 子句,甚至还可以有一个 finally 子句。这些会在关闭资源之后执行。
异常使用技巧
-
异常处理不能代替简单的测试
与简单的测试相比,捕获异常所花费的时间远大于普通检测代码,因此使用异常的基本规则是:只在异常情况下使用异常。
-
不要过分地细化异常
将正常处理与错误处理分开。 -
充分利用异常层次结构
-
不要只抛出 RuntimeException 异常,应该寻找一个合适的子类或创建自己的异常类
-
不要只捕获 Throwable 异常,否则将会使得代码更难度、更难维护
-
考虑检查型异常与非检查型异常的区别。检查型异常本来就庞大,不要为逻辑错误抛出这些异常。
-
-
不要压制异常
-
检测错误时,“苛刻”比放任更好
栈空调用 Stack.pop 时,返回null还是抛出异常?一般认为:最好在出错的地方抛出一个 EmptyStackException异常,要好于以后抛出一个空指针异常。 -
不要羞于传递异常