背景
在Java中,如果打开了外部资源(文件、数据库连接、网络连接等),因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制,我们必须在这些外部资源使用完毕后,手动关闭它们。如果我们不在编程时确保在正确的时机关闭外部资源,就会导致外部资源泄露,紧接着就会出现文件被异常占用,数据库连接过多导致连接池溢出等诸多很严重的问题
传统关闭方式
为了确保外部资源一定要被关闭,通常关闭代码被写入finally代码块中,我们还要考虑到关闭资源时可能抛出的异常,并且抛出异常时我们还要确保其他资源可以正常关闭,于是变有了下面的经典代码
public class TryWithResourceDemo {
public static void main(String[] args) {
BufferedInputStream bin = null;
BufferedOutputStream bout = null;
try {
bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")));
int b;
while ((b = bin.read()) != -1) {
bout.write(b);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bin != null) {
try {
bin.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bout != null) {
try {
bout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
}
这简直就是噩梦……关闭资源比业务代码都长,这大佬肯定忍不了,所以就有了try-with-resource来简化大量的样板式代码
public class TryWithResourceDemo {
public static void main(String[] args) {
try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
int b;
while ((b = bin.read()) != -1) {
bout.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
wow~这就是try-with-resource,我们把需要用到的资源连接放在try语句后面的小括号里,这个语法可以确保当try语句执行完毕后会把小括号里面的连接全部关闭,是不是很酷。但这并不是什么新的机制,是编译器帮我们做了工作,和foreach一样,这也是一个语法糖,编译器会生成和我们手写的一样的try-catch-finally语句(手头没有可以反编译的工具,所以没办法看代码),我们可以使用链式语句来让编译器生成尽量少的语句块,但前提是我们要足够了解不同对象内部对close方法的实现
使用try-with-resource的前提
- 资源必须实现
AutoClosable
接口 - JDK1.7及以上的版本
注意
在使用try-with-resource的过程中,一定需要了解资源的close方法内部的实现逻辑,否则还是可能会导致资源消耗
private static void compress(String input, String output) throws IOException {
try(
FileInputStream fin = new FileInputStream(input);
GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(output))
) {
byte[] buffer = new byte[4096];
int nread = 0;
while ((nread = fin.read(buffer)) != -1) {
out.write(buffer, 0, nread);
}
}
}
这是GZIPOutputStream类的close方法,大家可以看到在关闭out之前还有一个finish
操作,如果这个操作发生了异常会导致out没有被关闭
public void close() throws IOException {
if (!closed) {
finish();
if (usesDefaultDeflater)
def.end();
out.close();
closed = true;
}
}
- 正确姿势
private static void compress(String input, String output) throws IOException {
try (
FileInputStream fin = new FileInputStream(input);
FileOutputStream fout = new FileOutputStream(output);
GZIPOutputStream out = new GZIPOutputStream(fout)
) {
byte[] buffer = new byte[4096];
int nread = 0;
while ((nread = fin.read(buffer)) != -1) {
out.write(buffer, 0, nread);
}
}
}
单独声明资源,这样编译器会为每一个对象都生成try-catch-finally语句块来确保每一个资源都会被关闭
官网的详细介绍(中文)try-with-resources