Java核心技术:异常、断言和日志——使用异常机制的技巧
书中虽明确列出了使用异常机制的技巧,但这些技巧并不是看了就能掌握的,要想使用好这些技巧还是要在编程中练习、感悟
- 异常处理不能代替简单的测试
示例编写一段代码,试着上百万次对一个空栈进行退栈操作。
- 在试试退栈操作前,首先查看栈是否为空。
if (!s.empty())
s.pop();
- 强行进行退栈操作,捕获哦哦EmptyStackException异常来告知不能这样做。
try
{
s.pop();
}
catch(EmptyStackException e)
{
}
在测试的机器上,调用isEmpty的版本运行时间内为646毫秒,捕获EmptyStackException的版本运行时间为21 739毫秒。
与执行简单的测试相比,捕获异常所花费的时间大大超过了前者,因此使用异常基本规则是:只在异常情况下使用异常机制。
2. 不要过分地细化异常
错误示例:
PrintStream out;
Stack s;
for (i = 0; i < 100; i++)
{
try
{
n = s.pop();
}
catch (EmptyStackException e)
{
// stack was empty
}
try
{
out.writeInt(n);
}
catch (IOException e)
{
// problem writing to file
}
}
这种编程方式将导致代码量的急剧膨胀。如果栈是空的,则不能变为非空状态;如果文件出错,则也很难给予排除。出现上述问题后,这种编程方式无能为力。因此,有必要将整个任务包装在一个try语句块中,这样,当任何一个操作出现问题时,整个任务都可以取消。
try
{
for ( i = 0; i < 100; i++)
{
n = s.pop();
out.writeInt(n);
}
}
catch (IOException e)
{
// problem writing to file
}
catch (EmptyStackException e)
{
// stack was empty
}
- 利用异常层次结构
**不要只抛出RuntimeException异常。**应该寻找更加适当的子类或创建自己的异常类。
**不要只捕获Throwable异常。**否则,会使程序代码更难度、更难维护。
**考虑受查异常与非受查异常的区别。**已检查异常本来就很庞大,不要为逻辑错误抛出这些异常。
将一种异常转为为另一种更加合适的异常时不要犹豫。例如,在解析某个文件中的一个整数时,捕获NumberFormatException异常,然后将它转为为IOException或MySubsystemException的子类。 - 不要压制异常
在Java中,往往强烈地倾向关闭异常。如果编写了一个调用另一个方法的方法,而这个方法可能100年才抛出一个异常,那么编译器会因为没有将这个异常列在throws表中产生抱怨。而没有将这个异常列在throws表中主要出于编译器将会对所有调用这个方法的方法进行异常处理的考虑。因此,应该将这个异常关闭:(这个方法可能100年才抛出一个异常,为了避免编译对所有调用这个方法进行异常处理检查,所以不要在这个方法的签名中throws异常,但编译器会要求处理该异常(捕获或者抛出),所以,应该捕获这个异常但不再向外抛出异常)
public Image loadImage(String s)
{
try
{
// code that threatens to throw checked exceptions
}
catch (Exception e)
{} // so there
}
现在,这段代码就可以通过编译了(通过编译器检查)。除非发生异常,否则它将可以正常运行。即使发现异常也会被忽略。如果认为异常非常重要,应该对它们进行处理。
- 在检测错误时,“苛刻”要比放任更好
在用无效的参数调用一个方法时,返回一个虚拟的数值,还是抛出一个异常,哪种处理方式更好?例如,当栈空时,Stack.pop是返回一个null,还是抛出一个异常?我们认为:在出错的地方抛出一个EmptyStackException异常要比在后面抛出一个NullPointerException异常更好。 - 不要羞于传递异常
很多程序员都感觉应该捕获抛出的全部异常。如果调用了一个抛出异常的方法,例如,FileInputStream构造器或readLine方法,这些方法就会本能地捕获这些可能发生的异常。其实,传递异常要比捕获这些异常更好:
public void readStuff(String filename) throws IOException // not a sign of shame!
{
InputStream in = new FileInputStream(filename);
...
}
让高层次的方法通知用户发生了错误,或者放弃不成功的命令更加适宜。