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语句块和释放资源时可能发生的各种异常。