Java中请优先使用try-with-resources而非try-finally

Java库包含了很多需要手工调用close方法来关闭的资源。比如说InputStream、OutputStream及java.sql.Connection。关闭资源常常会被客户端所忽视,这会导致可怕的性能问题。虽然很多资源使用了终结器来作为安全网,不过终结器却并不那么尽如人意。

从历史上来看,try-finally语句是确保资源会被恰当关闭的最佳方式,即便在遇到异常或是返回语句时亦如此:

// try-finally - No longer the best way to close resources!

static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}

看起来还不错,不过当添加了第二个资源时情况就变得有些糟糕了:

// try-finally is ugly when used with more than one resource!
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}

虽然难以相信,但即便是优秀的程序员很多时候也会编写这样的代码。对于初学者来说,我在Java Puzzlers [Bloch05]的第88页指出了问题,但多年来没人注意到。事实上,2007年,在Java库中对close方法的使用有2/3是错误的。

甚至使用try-finally语句关闭资源的正确代码(如前面两个代码示例所示)也存在一些小问题。try块与finally块中的代码都可以抛出异常。比如说,在firstLineOfFile方法中,由于底层物理设备的失败,调用readLine可能会抛出异常,调用close也会因同样的原因而失败。在这些情况下,第二个异常完全会掩盖掉第一个。在异常堆栈中没有对第一个异常的记录信息,这会极大增加真实系统的调试复杂度——通常来说,第一个异常才是问题诊断的关键所在。虽然可以通过编写代码来压制第二个异常,从而保留第一个异常信息,但没人会这么做,因为太麻烦了。

所有这些问题都随着Java 7引入try-with-resources语句得到了解决[JLS, 14.20.3]。要想使用该结构,资源必须要实现AutoCloseable接口,该接口包含了唯一一个返回void类型的close方法。Java库与第三方库中的很多类和接口现在都实现或是继承了AutoCloseable。如果编写一个用于表示资源的类,它必须要关闭,那么这个类就应该实现AutoCloseable。

如下代码使用try-with-resources改写了上面第一个示例:

// try-with-resources - the the best way to close resources!

  static String firstLineOfFile(String path) throws IOException {

    try (BufferedReader br = new BufferedReader(

        new FileReader(path))) {

      return br.readLine();

}

}

如下代码使用try-with-resources改写了上面第二个示例:

// try-with-resources on multiple resources - short and sweet

  static void copy(String src, String dst) throws IOException {

    try (
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst))
{
byte[] buf = new byte[BUFFER_SIZE]; int n; while ((n = in.read(buf)) >= 0) out.write(buf, 0, n);
} }

try-with-resources版本不仅代码更简洁,可读性比原来的要好,而且提供了更好的问题诊断能力。考虑firstLineOfFile方法。如果readLine调用与(不可见的)close方法都抛出了异常,那么后者的异常就会被前者压制住。事实上,多个异常都会被压制住,从而保留你实际想看的那个异常。这些被压制的异常并不是被丢弃掉;他们会在堆栈中打印出来,并且有相应的符号表示他们被压制了。你还可以通过编程的方式通过getSuppressed方法来访问这些异常,该方法是在Java 7中被添加到Throwable中的。

你可以将catch从句放到try-with-resources语句上,就像常规的try-finally语句一样。这样就可以在处理异常的同时又不会在另一个嵌套层次上搞乱代码了。举个例子,下面是不抛出异常的firstLineOfFile方法版本,不过如果无法打开文件或是无法读取文件,那么它会接收一个默认值来返回:

// try-with-resources with a catch clause

   static String firstLineOfFile(String path, String defaultVal) {

       try (BufferedReader br = new BufferedReader(

               new FileReader(path))) {

           return br.readLine();

       } catch (IOException e) {

           return defaultVal;

	}
}

很显然,在处理需要关闭的资源时,请优先使用try-with-resources而非try-finally。结果代码更短、也更清晰,它所生成的异常也更加有用。try-with-resources语句使得编写使用了必须要关闭的资源的代码更加轻松,而这是try-finally所做不到的。

Java中请优先使用try-with-resources而非try-finally的更多相关文章

  1. java中请给一个Abstract类实现接口的实例!

    2.Abstract类实现接口 马克-to-win:如果实现某接口的类是abstract类,则它可以不实现该接口所有的方法.但其非abstract的子类中必须拥有所有抽象方法的实在的方法体:(当然它a ...

  2. java中请给出一个return this的例子。

    [新手可忽略不影响继续学习]下面例子中setYear中的return this;返回了一个指向对象的指针,this.setMonth(8).setDay(20);是合法的,如果像原来的例子一样什么都不 ...

  3. java中请给出一个抽象类,可以继承实体类的例子

    例1.7.2(抽象类可以继承实体类)- class VehMark_to_win {    void steer() {        System.out.println("Turn st ...

  4. java中请给出例子程序:找出两个数的最大公约数和最小公倍数

    9.2 找出12和8的最大公约数和最小公倍数.     public class Test {     public static void main(String[] args) {         ...

  5. java中请给出例子程序:找出n到m之间的质数。

    9.1 找出100到200之间的质数.  public class Test {     public static void main(String[] args){         for (in ...

  6. 关于java中为什么尽量把受检异常转化为非受检异常

    首先理解一下受检异常与非受检异常: 异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机操作中可能遇到的异常,是一种常见的运行错误,只要程序设计的没有问题通常就不会发生.受检异常与程序的上 ...

  7. Java中静态变量、静态代码块、非静态代码块以及静态方法的加载顺序

    在研究单例设计模式的时候,用到了静态变量和静态方法的内容,出于兴趣,这里简单了解一下这四个模块在类初始化的时候的加载顺序. 经过研究发现,它们的加载顺序为: 1.非静态代码块 2.静态变量或者静态代码 ...

  8. Java中的资源文件加载方式

    文件加载方式有两种: 使用文件系统自带的路径机制,一个应用程序只能有一个当前目录,但可以有Path变量来访问多个目录 使用ClassPath路径机制,类路径跟Path全局变量一样也是有多个值 在Jav ...

  9. c++中继承和java中继承的对比

    java中: class Parent{ public void test(int a){ System.out.println("Parent:" + a); System.ou ...

随机推荐

  1. CPU|MICGPU|FPGA|超算|Meta-data|

    生物医学大数据: 收集数据后对数据的分析,如同看相,而对数据信息的挖掘可以看作是算命.这两个过程是基于算法和软件这类工具之上的. 在存储方面:在硬件上,为了Parallel computing的目的, ...

  2. python socket实例

    1.客户端向服务端发送 #coding:utf-8 '''客户端''' import socket khd=socket.socket() #声明socket类型,同时生产socket连接对象 khd ...

  3. JavaIO 流(1)IO流介绍

    IO流定义: 流的本质是一组单向有序,分起始和终止的数据传输过程.需要导入import java.io.* IO流分类: 按数据类型分为:字节流和字符流 字节流: 按字节进行读取(可以处理任意类型数据 ...

  4. MatterTrack Route Of Network Traffic :: Matter

    Python 1.1 基础 while语句 字符串边缘填充 列出文件夹中的指定文件类型 All Combinations For A List Of Objects Apply Operations ...

  5. Android 代码混淆规则

    1. Proguard介绍 Android SDK自带了混淆工具Proguard.它位于SDK根目录toolsproguard下面.ProGuard是一个免费的Java类文件收缩,优化,混淆和预校验器 ...

  6. golang实现chunk方式的查询

    有一个需求,是把表里面所有的数据都查询出来,并且生成json文件.因为一张表里面的数据很多,所以不可能一次性全部查询出来,所以需要用到chunk.之前用的gorm,但是发现gorm没有chunk方式的 ...

  7. C++与引用2

    */ * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:text.cpp * 作者:常轩 * 微信公众号:Worldhe ...

  8. ndarray数组的索引和切片

    索引:获取数组中特定位置元素的过程 切片:获取数组元素子集的过程 import numpy as np 一维数组 一维数组的索引和切片与python中的列表类似 索引:若元素个数为n,则索引下标可表示 ...

  9. 达拉草201771010105《面向对象程序设计(java)》第九周学习总结

    达拉草201771010105<面向对象程序设计(java)>第九周学习总结 实验九异常.断言与日志 实验时间 2018-10-25 1.实验目的与要求 (1) 掌握java异常处理技术: ...

  10. 微信小程序支付到第三方商户账号

    使用场景:合作商家使用本公司小程序开店,要求支付金额直接到合作商家的公司微信账户; 使用要求:合作商家需提供微信支付关联,商户号,商户API密钥,API证书(该证书只用作退款功能,不开发退款可以不用) ...