日常开发中我们无法回避对异常的处理,Java的异常处理有一些令人模糊的地方,详情见下:
正确关闭资源
在实际的开发中,经常需要在程序中打开一些物理资源,如数据库连接,网络连接,磁盘文件等,打开这些物理资源后必须显示关闭资源,否则将造成资源泄漏
finally
{
if(oos 1= null)
{
try{
os.close();
}
catch (Excaption ex)
{
ex.printStackTrace();
}
}
if(ols != null)
{
ols.close();
}
catch(Exception ex)
{
ex.printstacktrace();
}
}
}
上面程序所示的资源关闭方式才是比较安全的,这种关闭方式主要保证如下三点。
>使用finally块来关闭物理资源,保证关闭操作总是会被执行。
>关闭每个资源之前首先保证引用该资源的引用变量不为nul。
>为每个物理资源使用单独的try/.catch块来关闭资源,保证关闭资源时引发的异常不会影响其他资源的关闭。
使用Java7增强的try语句关闭资源
使用上面的方式来关闭资源,必然导致finally块代码十分臃肿,这样的代码必将导致程序的可读性降低
方式:它允许在try关键字后紧跟一对圆括号,圆括号可以声明、初始化一个或多个资源
细节:此处的资源指的是那些必须在程序结束时显式关闭的资源(比如数据库连接、网络连接等),try语句会在该语句结束时自动关闭这些资源。
需要指出的是,为了保证try语句可以正常关闭资源,这些资源实现类必须实现AutoCloseable 或Closeable接口,实现这两个接口就必须实现close()方法。
自动关闭资源的try语句相当于包含了隐式的fmally块(这个finally块用于关闭资源),因此这个try语句可以既没有catch块,也没有finally块。
需要指出的是,使用自动关闭资源的try语句有两个注意点:
》被自动关闭的资源必须实现Closeable或AutoClosable接口。
》被自动关闭的资源必须放在try 语句后的圆括号中声明、初始化。
如果程序需要,自动关闭资源的try 语句后 也可以带多个catch块和一个finally块。
finally块的陷阱
当程序中的try块代码里增加了System.exit(0)来退出程序
结果:程序并不会执行finally块
原理上是:不论try块是正常结束,还是中途非正常退出,finally块确实都会执行。然而在这个程序中,try语句块根本就没有结束其执行过程,System.exit(0);将停止当前线程和所有其他当场死亡的线程。finally块并不能让已经停止的线程继续执行。
原理:{
》 执行系统中注册的所有关闭钩子。
》如果程序调用了Systen.runFinaizcef0nExit(true),那么JVM会对所有还未结束的对象调用Finalizer
第一种方式则是一种安全的操作,程序可以将关闭资源的操作注册成为关闭钩子。在JVM退出之前,这些关闭钩子将会被调用,从而保证物理资源被正常关闭。
public class ExitHook
{
public static void main(String[]args)
throws IOBxception
{
final FileOutputStream fos;
fos=new FileOutputStream("a.bin");
System.out.print1n("程序打开物理资源!");
//为系统注册关闭钩子
Runtime.getRuntime().addshutdownlook(
new Thread()
{
public void run()
{ //使用关闭钩子来关闭资源
if(fos!=nul1)
{ try
{
fos.close();
}
catch(Exception ex)
{
ex.printStackrrace();
}
}
system.out.print1n(“程序关闭了物理资源!");
}
});
System.exit(0);//将exit放在此处,保证执行关闭钩子在之前
}
}
finally块和方法返回值
总结上面的,我们发现:只要JVM未结束,不论try块是正常结束,还是中途非正常退出,finally块确实都会执行
当finally语句遇到return时会发生什么?
当Java程序执行try块、catch块时遇到了return 语句,returm语句会导致该方法立即结束。
系统执行完return语句之后,并不会立即结束该方法,而是去寻找该异常处理流程中是否包含finally块.
》如果没有finally块,则方法终止,返回相应的返回值;
》1.果有finally块,系统会立即开始执行finally块—(当是普通的finally语句时)只有当finally块执行完成后,系统才会再次跳回来根据 return语句结束方法。
如果finally块里使用了retum语句来导致方法结束,则finally块已经结束了方法,系统将不会跳回去执行try块、catch块里的任何代码
(特别的,当return只在finally语句里,有异常会立马执行finally块,只有当finally块执行完毕后,才会跳回来抛出异常,而finally块里的return语句会立即结束方法,所以即使try块里有引发中止的异常,JVM也不会去返回去抛出异常,中止)
-------------------------------------------------------------------------------------------
catch块的顺序
进行异常捕捉时,一定要记住先捕捉小异常,再捕捉大异常,即try,catch语句的多个catch块应该先捕捉子类异常,后捕捉父类异常,否则编译器会提示编译错误
不要用catch块代替流程控制
譬如把break写入catch块里
只有catch可能抛出的异常
根据Java语言规范,如果一个catch块试图捕捉一个类型为XxxException的checked异常,那么它对应的try块必须可能抛出XxxException或则它的子类的异常,否则编译器将提示具有编译错误-----但在所有checked异常中,Exception是个异类,无论try块是怎样的代码,catch(Exception ex)总是正确的
Runtime异常和Checked异常
RuntimeException类及其子类的异常,的实例被称为RUntime异常,不是RuntimeException类及其子类的异常被称为checked异常
实际上,如果一段代码可能抛出某个Checked异常(这段代码调用的某个方法、构造器声明抛出了该Checked异常),那么程序必须处理这个Checked异常。对于Checked异常的处理方式有两种。
》当前方法明确知道如何处理该异常,程序应该使用try.catch块来捕捉该异常,然后在对应的catch块中修复该异常。
》当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。
总之,程序使用catch块捕捉异常时,其实并不能随心所欲地捕捉所有异常。程序可以在
任意想捕捉的地方捕捉RuntimeException异常,但对于其他Checked异常,只有当try块可能
抛出该异常时(try块中调用的某个方法声明抛出了该Checked异常),catch块才能捕捉该
Checked异常。
做点实际的修复
在catch语句中做实际修复的时候,不要试图重新调用该方法,否则会造成递归调用,栈溢出错误
继承得到的异常
Java语言规定,子类重写父类的方法时,不能声明抛出的比父类方法类型更多,范围更大的异常,也就是说,子类方法只能声明抛出父类方法所声明抛出的异常的子类。
------------------------------------------------------------------------------------------------
Java7增强的throw语句
在Java7以前,Java编译器的处理“简单而粗暴”:由于你在捕捉该异常时声明ex的类是Exception,因此Java编译器认为这段代码可能抛出Exception异常,因此包含这段代码方法通常需要声明抛出Exception异常。
从Java7开始,Java编译器会执行更细致的检查,Java编译器会检查throw语句抛出的异常的实际类型,这样编译器就会知道①号代码实际上只可能抛出FileNotFoundException异常,因此在方法签名中只要声明抛出FileNotFoundException异常即可。即代码可以改为如下形式。
public class ThrowTest2
{
public static void main(String[]args)
//Java7会检查①号代码可能抛出异常的实际类型
//因此该方法只需声明抛出FileNotFoundException异常即可
throws FileNotFoundExcoption{
try{
new FileoutputStream("a.txt");
}
catch(Exception ex)
{
ex.printStackrrace();
throw ex;
}
}
}