前言
Java中外部资源无法使用JVM垃圾回收机制回收,例如数据库连接、网络连接、文件等,因此使用完这些资源需要手动关闭。
Java SE 7 之前关闭外部资源
无论是try语句是正常还是异常完成,finally都会确保关闭资源,如下例子: 例一:
public static List<String> list(String filePath, Integer start, Integer limit){
File file = new File(filePath);
Stream<String> stream = null;
try {
stream = Files.lines(Paths.get(file.getPath()));
return stream.skip(start).limit(limit).collect(Collectors.toList());
} catch (Exception e) {
logger.error("exception-->fileName:{} error:{}", file.getName(), e.getMessage());
}finally {
if(null != stream){
stream.close();
}
}
return null;
}
从例一看到,try中stream操作异常和finally中close都抛出异常,那么方法则抛出finally异常,从try块中抛出的异常将被抑制。
Java SE 7 及之后关闭外部资源新方法
在Java SE 7 及之后, java提供了另外一种语法来关闭资源,使用try-with-resources语句,先来下例一使用此语句的写法。 例二:
public static List<String> list(String filePath, Integer start, Integer limit){
File file = new File(filePath);
try (Stream<String> stream = Files.lines(Paths.get(file.getPath()))){
return stream.skip(start).limit(limit).collect(Collectors.toList());
} catch (Exception e) {
logger.error("exception-->fileName:{} error:{}", file.getName(), e.getMessage());
}
return null;
}
我们可以看到例二看起来比例一简洁了很多。try后面的括号里面的资源是Stream(jdk1.8标准库中提供的类),Files.lines(jdk1.8中提供的方法)方法从文件中读取所有的内容返回Stream。因为Stream是在try-with-resources语句中声明,无论try语句是正常完成还是异常停止的话,资源都会被关闭。
我们打开Stream类,可以看到类的注解上有这么一段注释:
<p>Streams have a {@link #close()} method and implement {@link AutoCloseable},
* but nearly all stream instances do not actually need to be closed after use.
* Generally, only streams whose source is an IO channel (such as those returned
* by {@link Files#lines(Path, Charset)}) will require closing. Most streams
* are backed by collections, arrays, or generating functions, which require no
* special resource management. (If a stream does require closing, it can be
* declared as a resource in a {@code try}-with-resources statement.)
可以看出file.lines是需要关闭流的,并且建议我们可以声明为try-with-resources来关闭文件(就像我们例二中的例子)。
另外我们看到Stream这个类继承了AutoCloseable。
那么try-with-resource语句是怎么在打开声明的资源后进行关闭的。我们进入Files.lines方法看一下:
public static Stream<String> lines(Path path, Charset cs) throws IOException {
BufferedReader br = Files.newBufferedReader(path, cs);
try {
return br.lines().onClose(asUncheckedRunnable(br));
} catch (Error|RuntimeException e) {
try {
br.close();
} catch (IOException ex) {
try {
e.addSuppressed(ex);
} catch (Throwable ignore) {}
}
throw e;
}
}
我们看到br.lines()返回Stream, 后面调用了onClose()方法,onClose方法参数是close handler。我们看下参数代码:
/**
* Convert a Closeable to a Runnable by converting checked IOException
* to UncheckedIOException
*/
private static Runnable asUncheckedRunnable(Closeable c) {
return () -> {
try {
c.close();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
}
看到onClose方法为返回的Stream设置了close handler, 在close handler里面调用了close方法来关闭文件。
再来看下onClose方法:
/**
* Returns an equivalent stream with an additional close handler. Close
* handlers are run when the {@link #close()} method
* is called on the stream, and are executed in the order they were
* added. All close handlers are run, even if earlier close handlers throw
* exceptions. If any close handler throws an exception, the first
* exception thrown will be relayed to the caller of {@code close()}, with
* any remaining exceptions added to that exception as suppressed exceptions
* (unless one of the remaining exceptions is the same exception as the
* first exception, since an exception cannot suppress itself.) May
* return itself.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param closeHandler A task to execute when the stream is closed
* @return a stream with a handler that is run if the stream is closed
*/
S onClose(Runnable closeHandler);
我们可以看到onClose是流关闭时需要执行的任务,返回的是带有close handler的流,close handler在流关闭的时候执行。
再来看下Stream的close方法,如下所示(Stream的close方法):
/**
* Closes this stream, causing all close handlers for this stream pipeline
* to be called.
*
* @see AutoCloseable#close()
*/
@Override
void close();
注释明确表示了close方法会调用所有的close handlers。 至此我们明白了,例二就是返回了带有close handler的Stream,try-with-resource语句会自动调用资源的close方法,会触发所有的close handlers,进而实现了关闭流的操作。
try-with-resource声明多个资源,关闭顺序是怎么样的
这个我们我们可以从官方文档的例子说明下(下面的例子是从官方文档例子摘抄,可查看下面的参考文档链接): 例三:
public static void writeToFileZipFileContents(String zipFileName,
String outputFileName)
throws java.io.IOException {
java.nio.charset.Charset charset =
java.nio.charset.StandardCharsets.US_ASCII;
java.nio.file.Path outputFilePath =
java.nio.file.Paths.get(outputFileName);
// Open zip file and create output file with
// try-with-resources statement
try (
java.util.zip.ZipFile zf =
new java.util.zip.ZipFile(zipFileName);
java.io.BufferedWriter writer =
java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
) {
// Enumerate each entry
for (java.util.Enumeration entries =
zf.entries(); entries.hasMoreElements();) {
// Get the entry name and write it to the output file
String newLine = System.getProperty("line.separator");
String zipEntryName =
((java.util.zip.ZipEntry)entries.nextElement()).getName() +
newLine;
writer.write(zipEntryName, 0, zipEntryName.length());
}
}
}
try-with-resources被抑制的异常
另外我们再简单提一下异常抑制,在例二中如果try块和try-with-resource语句都发生异常,那么整个方法会抛出try块的异常。try-with-resource语句抛出的异常将会被抑制。在Java SE 7以及更高版本中,可以检索被抑制的异常,可以通过Throwable.getSupressed方法从try块抛出的异常检索被抑制的异常。
我们可以看下例二通过编译生成的.class文件的代码:
public static List<String> list(String filePath, Integer start, Integer limit) {
File file = new File(filePath);
try {
Stream<String> stream = Files.lines(Paths.get(file.getPath()));
Throwable var4 = null;
List var5;
try {
var5 = (List)stream.skip((long)start).limit((long)limit).collect(Collectors.toList());
} catch (Throwable var15) {
var4 = var15;
throw var15;
} finally {
if (stream != null) {
if (var4 != null) {
try {
stream.close();
} catch (Throwable var14) {
var4.addSuppressed(var14);
}
} else {
stream.close();
}
}
}
return var5;
} catch (Exception var17) {
logger.error("exception-->fileName:{} error:{}", file.getName(), var17.getMessage());
return null;
}
}
我们可以看到编译后的代码其实也是使用了finally块,我们来看下var4.addSuppressed(var14)这一行。从这行可以看出try-with-resource关闭资源时抛出的异常并没有丢失,而是添加到了抛出异常的Throwable集合中(List suppressedExceptions),我们可以通过抛出异常的e.getSuppressed得到被抑制的异常数组。
总结
通过上面的讲解我们对the try-with-resources statement有了一定的认识,总结为下面五点:
1.任何实现java.lang.AutoCloseable或者java.io.Closeable(Closeable继承了AutoCloseable)接口都可以作为try-with-resources资源。
2.try-with-resources语句是声明一个或者多个资源的try语句,程序完成该语句会自动调用资源的close方法来确保资源在结束时关闭。
3.try-with-resources会比Java SE 7之前通过finally块进行关闭更加简洁(通过编译后的代码可以看到也是通过fianally来实现的)。
4.try-with-resources声明的资源和try块代码如果都抛出异常,Java SE 7以及后面更高版本,可以通过Throwable.getSupressed检索被抑制的异常。
5.如果try-with-resources声明了多个资源,资源的close方法的调用和资源声明的顺序是相反的。
参考文档链接: docs.oracle.com/javase/tuto…