程序中因错误而抛出异常时,原本的执行流程就会中断,抛出异常处之后的程序代码就不会被执行,如果程序开启了相关资源,使用完毕后应当关闭资源。若因错误而抛出异常,应当考虑是否还能正确地关闭资源。
1、使用 finally
如果创建 FileInputstream实例就会开启文档,不使用时,应该调用 close()关闭文档。 Fileutil中是通过 Scanner搭配 FileInputstream来读取文档,实际上 Scanner()对象有个 close()方法,可以关闭 Scanner相关资源与搭配的Fileinputstream。
例如:
package errorDemo;
import java.io.*;
import java.util.*;
public class FileUtil {
public static String readFile(String name) throws FileNotFoundException {
StringBuilder txt = new StringBuilder();
Scanner scan = new Scanner(new FileInputStream(name));
while (scan.hasNext()) {
txt.append(scan.nextLine()).append('\n');
}
scan.close();
return txt.toString();
}
}
如果 scanner. close()前发生了任何异常,执行流程就会中断,因此 scanner. close()就可能不会执行,因此 Scanner搭配的 Fileinputstream?就不会被关闭。
你想要的是无论如何,最后一定要执行关闭资源的动作,try、 catch语法还可以搭配finally,无论try区块中有无发生异常,若撰写有 finally区块,则finally区块一定会被执行。例如:
package errorDemo;
import java.io.*;
import java.util.*;
public class FileUtil {
public static String readFile(String name) throws FileNotFoundException {
StringBuilder txt = new StringBuilder();
Scanner scan = null;
try {
scan = new Scanner(new FileInputStream(name));
while (scan.hasNext()) {
txt.append(scan.nextLine()).append('\n');
}
} finally {
if(scan!=null) {
scan.close();
}
}
return txt.toString();
}
}
由于finaly区块一定会被执行,这个范例中 scanner原先是null,若 FileInputStream创建失败,则 scanner就有可能还是null,因此在 finally区块中必须先检查 scanner是否有参考对象,有的话才进一步调用 close()方法,否则scanner参考至null又打算调用close方法,反而会抛出 NullPointerException。
如果程序撰写的流程中先return了,而且也有finally区块,那 finally区块会先执行完后,再将值返回。例如,下面这个会先显示 ggg再显示1:
package errorDemo;
public class FinallyDemo {
public static void main(String[] args) {
System.out.println(test(true));
}
static int test(boolean flag) {
try {
if(flag) {
return 1;
}
} finally {
// TODO: handle finally clause
System.out.println("ggg");
}
return 0;
}
}
2、自动尝试关闭资源
在使用try、 finally尝试关闭资源时,会发现程序撰写的流程是类似的,就如先前 FileUtil,你会先检查 scanner是否为null,再调用close()方法关闭 Scanner。在JDK7之后,新增了尝试关闭资源 (try-with- Resources)语法
package errorDemo;
import java.io.*;
import java.util.*;
public class FileUtil2 {
public static String readFile(String name) throws FileNotFoundException {
StringBuilder txt = new StringBuilder();
try (Scanner scan = new Scanner(new FileInputStream(name))) {
while (scan.hasNext()) {
txt.append(scan.nextLine()).append('\n');
}
}
return txt.toString();
}
}
想要尝试自动关闭资源的对象,是撰写在try之后的括号中,如果无须 catch处理任何异常,可以不用撰写,也不用撰写finally自行尝试关闭资源。JDK7的尝试关闭资源语法是编译程序蜜糖,尝试反编译:
...
public class FileUtil2 {
public static String readFile(String name) throws FileNotFoundException {
StringBuilder txt = new StringBuilder();
Scanner scan = new Scanner(new FileInputStream(name));
Throwable localThrowable2 = null;
try{
while (scan.hasNext()) {
txt.append(scan.nextLine()).append('\n');
}
}catch(Throwable LocalThrowable1){//捕捉所有错误
localThrowable2 = LocalThrowable1;
throw LocalThrowable1;
}
finally{
if(scan!=null){//如果scan参考了Scanner实例
if(localThrowable2!=null){//若前面catch到其他异常
try{
scan.close();//尝试关闭Scanner实例
}catch(Throwable x2){//万一关闭时发生错误
localThrowable2.addSuppressed(x2);//在原异常对象中记录
}
}else{
scan.close();//若前面没有发生任何异常,直接关闭
}
}
}
return txt.toString();
}
}
若一个异常被 catch后的处理过程引发另一个异常,通常会抛出第一个异常作为响应, addSuppresse()方法是JDKT在java.lang.Throwable中新增的方法,可将第二个异常记录在第一个异常之中,JDK7中与之相对应的是 getSuppressed()方法,可返回 Throwable[],代表先前被addSuppressed()记录的各个异常对象。
使用自动尝试关闭资源语法时,也可以搭配 catch。如在发生FileNotFoundException 时显示堆栈追踪信息:
public static String readFile(String name) throws FileNotFoundException {
StringBuilder txt = new StringBuilder();
try (Scanner scan = new Scanner(new FileInputStream(name))) {
while (scan.hasNext()) {
txt.append(scan.nextLine()).append('\n');
}
}catch(FileInputStream e){
e.printStackTrace();
throw e;
}
return txt.toString();
}
使用JAD反编译后可以看到,实际上前一个反编译程序片段中一部分,是产生在另一个try、 catch区块中:
...
public class FileUtil2 {
public static String readFile(String name) throws FileNotFoundException {
StringBuilder txt = new StringBuilder();
try{
Scanner scan = new Scanner(new FileInputStream(name));
Throwable localThrowable2 = null;
try {
while (scan.hasNext()) {
txt.append(scan.nextLine()).append('\n');
}
}catch(Throwable LocalThrowable1){
localThrowable2 = LocalThrowable1;
throw LocalThrowable1;
}
finally{
if(scan!=null){
if(localThrowable2!=null){
try{
scan.close();
}catch(Throwable x2){
localThrowable2.addSuppressed(x2);
}
}else{
scan.close();
}
}
}
}catch(FileInputStream ex){
ex.printStackTrace();
throw ex;
}
return txt.toString();
}
}
使用自动尝试关闭资源语法时,并不影响你对特定异常的处理。自动尝试关闭资源语法仅协助你关闭资源,而非用于处理异常。从反编译的程序代码中也可以看到,使用尝试关闭资源语法时,不要试图自行撰写程序代码关闭资源,这样会造成重复调用close()方法。
3 、java. lang. AutoCloseable
JDK7的尝试关闭资源语法可套用的对象,必须操作java.lang. AutoCloseable接口。它是JDK7新增的仅定义了close()的接口。
只要操作AutoCloseable接口就可以套用至尝试自动关闭资源语法:
package errorDemo;
public class AutoCloseableDemo {
public static void main(String[] args) {
try (Resource res=new Resource()){//括号内执行完自动关闭
res.doSome();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Resource implements AutoCloseable{
void doSome() {
System.out.println("gggg");
}
@Override
public void close() throws Exception {
System.out.println("资源关闭");
}
}
执行结果:gggg 资源关闭
尝试自动关闭资源语法可以关闭两个以上对象资源,中间以分号分开:
package errorDemo;
public class AutoCloseableDemo {
public static void main(String[] args) {
try (Resource res = new Resource(); Resource2 res2 = new Resource2()) {//try区间执行完毕后统一关闭资源
res.doSome();
res2.doOther();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Resource implements AutoCloseable {
void doSome() {
System.out.println("some");
}
@Override
public void close() throws Exception {
System.out.println("资源关闭");
}
}
class Resource2 implements AutoCloseable {
void doOther() {
System.out.println("other");
}
@Override
public void close() throws Exception {
System.out.println("资源2关闭");
}
}
执行结果:some other 资源2关闭 资源关闭
try括号中,越后面的对象资源越早被关闭。反编译发现每个AutoCloseable对象,都独立使用一个try、catch、finally,try括号越后面的对象会越在内层的try、catch、finally中。