1.5 try-with-resources语句

这一节将要介绍的是Java 7中引入的使用try语句进行资源管理的新用法。这一节的内容与上一节介绍的异常处理的关系比较密切。比如1.4.3节中介绍的Throwable中的新方法addSuppressed就是为try-with-resources语句添加的。对于资源管理,大多数开发人员都知道的一条原则是:谁申请,谁释放。这些资源涉及操作系统中的主存、磁盘文件、网络连接和数据库连接等。凡是数量有限的、需要申请和释放的实体,都应该纳入到资源管理的范围中来。

对于C++程序员来说,程序的内存管理是他们的一项职责。他们需要保证每一块申请的内存都在正确的时候得到了释放。要么在构造函数中申请,在析构函数中释放;要么使用类似智能指针一样的结构来实现资源管理。Java语言把内存管理的任务交给了Java虚拟机,通过自动垃圾回收机制减少了开发人员的很多工作。但是像输入输出流和数据库连接这样的资源,还是需要开发人员手动释放。

在使用资源的时候,有可能会抛出各种异常,比如读取磁盘文件和访问数据库时都可能出现各种不同的异常。而资源管理的一个要求就是不管操作是否成功,所申请的资源都要被正确释放。1.4.3节的代码清单1-10就是资源管理的经典案例,即通过try-catch-finally语句块的finally语句进行资源释放操作。这种方式虽然比较易懂,但是其中包含的冗余代码比较多。

为了简化这种典型的应用,Java 7对try语句进行了增强,使它可以支持对资源进行管理,保证资源总是被正确释放。代码清单1-18给出了一个读取磁盘文件内容的示例。

代码清单1-18 读取磁盘文件内容的示例


public class ResourceBasicUsage{

public String readFile(String path)throws IOException{

try(BufferedReader reader=new BufferedReader(new FileReader(path))){

StringBuilder builder=new StringBuilder();

String line=null;

while((line=reader.readLine())!=null){

builder.append(line);

builder.append(String.format("%n"));

}

return builder.toString();

}

}

}


上面的代码并不需要使用finally语句来保证打开的流被正确关闭,这是自动完成的。相对于传统的使用finally语句的做法,这种方式要简单得多。开发人员只需要关心使用资源的业务逻辑即可。资源的申请是在try子句中进行的,而资源的释放则是自动完成的。在使用try-with-resources语句的时候,异常可能发生在try语句中,也可能发生在释放资源时。如果资源初始化时或try语句中出现异常,而释放资源的操作正常执行,try语句中的异常会被抛出;如果try语句和释放资源都出现了异常,那么最终抛出的异常是try语句中出现的异常,在释放资源时出现的异常会作为被抑制的异常添加进去,即通过Throwable.addSuppressed方法来实现。

能够被try语句所管理的资源需要满足一个条件,那就是其Java类要实现java.lang.AutoCloseable接口,否则会出现编译错误。当需要释放资源的时候,该接口的close方法会被自动调用。Java类库中已有不少接口或类继承或实现了这个接口,使得它们可以用在try语句中。在这些已有的常见接口或类中,最常用的就是与I/O操作和数据库相关的接口。与I/O相关的java.io.Closeable继承了AutoCloseable,而与数据库相关的java.sql.Connection、java.sql.ResultSet和java.sql.Statement也继承了该接口。如果希望自己开发的类也能利用try语句的自动化资源管理,只需要实现AutoCloseable接口即可。代码清单1-19给出了一个自定义资源的使用示例,在close方法中可以添加所需要的资源释放逻辑。

代码清单1-19 自定义资源使用AutoCloseable接口的示例


public class CustomResource implements AutoCloseable{

public void close()throws Exception{

System.out.println("进行资源释放。");

}

public void useCustomResource()throws Exception{

try(CustomResource resource=new CustomResource()){

System.out.println("使用资源。");

}

}

}


除了对单个资源进行管理之外,try-with-resources还可以对多个资源进行管理。代码清单1-20给出了try-with-resources语句同时管理两个资源的例子,即经典的文件内容复制操作。

代码清单1-20 使用try-with-resources语句管理两个资源的示例


public class MultipleResourcesUsage{

public void copyFile(String fromPath, String toPath)throws IOException{

try(InputStream input=new FileInputStream(fromPath);

OutputStream output=new FileOutputStream(toPath)){

byte[]buffer=new byte[8192];

int len=-1;

while((len=input.read(buffer))!=-1){

output.write(buffer,0,len);

}

}

}

}


当对多个资源进行管理的时候,在释放每个资源时都可能会产生异常。所有这些异常都会被加到资源初始化异常或try语句块中抛出的异常的被抑制异常列表中。

在try-with-resource语句中也可以使用catch和finally子句。在catch子句中可以捕获try语句块和释放资源时可能发生的各种异常。