try-with-resource是从java7开始提供的新特性。方便了我们资源的代码编写。下面展示一下代码模板的对比
没有try-with-resource
try{
资源打开
业务逻辑
}catch(Exception e){
异常处理
}finally{
资源关闭
}
try-with-resource的
try(资源打开){
业务逻辑
}catch(Exception e){
异常处理
}
案例分析
看了上面的模板,我们的一个直观感觉是省去了finally,并且把资源放在了try后面的括号中。下面展示一段真实的代码。
public static void main(String[] args) {
try (
InputStream fileInputStream = Files.newInputStream(Paths.get("/Users/xie/delete/test.txt"));
OutputStream outputStream = Files.newOutputStream(Paths.get("/Users/xie/delete/test2.txt"));
) {
byte[] bytes = new byte[1024];
int len;
while ((len = fileInputStream.read(bytes)) > 0) {
outputStream.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
注:上面的代码只是演示了一个文件拷贝的方法,如果我们自己写代码请选择1.7提供的Files.copy。
try-with-resource做了什么
我们通过反编译工具来做一下对比
public static void main(String[] args) {
try {
InputStream fileInputStream = Files.newInputStream(Paths.get("/Users/xie/delete/test.txt"));
Throwable var2 = null;
try {
OutputStream outputStream = Files.newOutputStream(Paths.get("/Users/xie/delete/test2.txt"));
Throwable var4 = null;
try {
byte[] bytes = new byte[1024];
int len;
while((len = fileInputStream.read(bytes)) > 0) {
outputStream.write(bytes, 0, len);
}
} catch (Throwable var30) {
var4 = var30;
throw var30;
} finally {
if (outputStream != null) {
if (var4 != null) {
try {
outputStream.close();
} catch (Throwable var29) {
var4.addSuppressed(var29);
}
} else {
outputStream.close();
}
}
}
} catch (Throwable var32) {
var2 = var32;
throw var32;
} finally {
if (fileInputStream != null) {
if (var2 != null) {
try {
fileInputStream.close();
} catch (Throwable var28) {
var2.addSuppressed(var28);
}
} else {
fileInputStream.close();
}
}
}
} catch (IOException var34) {
var34.printStackTrace();
}
}
基本从字节码反编译过来,我们可以看到他是按照我们熟悉的方式编写的代码。所以try-with-resource是语法糖。
这里大家不熟悉的可能就是一场处理里面有addSuppressed的调用。这是一个异常新加的方法,抑制异常。其实这种场景很常见,一个方法里执行出了多个异常,应该报哪个呢,虽然都能表示这个方法调用的失败。以前的做法就是自己catch然后做一些逻辑操作,最后抛出一个。有了这个方法就能实现以前的逻辑,逻辑中也可以取出被抑制的异常信息。
使用的注意事项
- 第三方库的资源使用需要了解,他的资源是否实现了Closeable。
jdk的资源是都实现了这个接口。
class OutputStream implements Closeable, Flushable {
只有实现了Closeable的才可以和try-with-resource搭配使用。
- 需要了解每个资源的关闭细节
这里需要列举两个情况。
- socket的流的关闭会导致socket关闭。
下面以inputstream为例。SocketInputStream集成FileInputStream,所以也实现了Closeable。
class SocketInputStream extends FileInputStream
但是他的close方法做的事情有点多,会检测socket是否关闭。
public void close() throws IOException {
if (closing)
return;
closing = true;
if (socket != null) {
if (!socket.isClosed())
socket.close();
} else
impl.close();
closing = false;
}
- 输出流关闭没有flush
这是我调用别人写的库的时候遇到的问题,一直发现文件超过一定大小就会传输丢失。习惯了很多库都是close会顺带帮你做flush。
上面的这些情况,本身就是使用流该注意的地方。
- 资源声明顺序
通过上面的例子,其实资源也是和我们自己写代码的思路一致,先声明的后关闭。
9版本的改进
java9之前的try-with-resource都是必须做一次赋值的。
public static void read(InputStream fileInputStream) {
try (InputStream fileInputStreamTmp = fileInputStream) {
byte[] bytes = new byte[1024];
int len;
while ((len = fileInputStreamTmp.read(bytes)) > 0) {
}
} catch (IOException e) {
e.printStackTrace();
}
}
这个问题也挺明显的,我都开始名字上加Tmp来标识了。
9之后就彻底不用这么做了
public static void read(InputStream fileInputStream) {
try (fileInputStream) {
byte[] bytes = new byte[1024];
int len;
while ((len = fileInputStream.read(bytes)) > 0) {
}
} catch (IOException e) {
e.printStackTrace();
}
}