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. python的拆包(扫盲)

    什么是拆包 拆包: 对于函数中的多个返回数据, 去掉元组, 列表 或者字典 直接获取里面数据的过程. 怎么拆包 1) 对列表进行拆包 my_list = [1, 3.14, "hello&q ...

  2. AVFoundation Programming Guide(官方文档翻译4)Editing - 编辑

    新博客:完整版 - AVFoundation Programming Guide 分章节版:- 第1章:About AVFoundation - AVFoundation概述- 第2章:Using A ...

  3. SSM框架三分钟搞定分页查询

    使用的国产第三方jar   pagehelper 里面的基本属性值 //当前页 private int pageNum; //每页的数量 private int pageSize; //当前页的数量 ...

  4. Beautiful Soup的用法(五):select的使用

    原文地址:http://www.bugingcode.com/blog/beautiful_soup_select.html select 的功能跟find和find_all 一样用来选取特定的标签, ...

  5. JavaIO 流(1)IO流介绍

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

  6. Django 学习笔记1-- URLconf

    今天好像巴黎有点乱,希望明天太阳还会照常升起. 简介 Django 是一个由 Python 编写.开源并采用经典的 MVC 设计模式的 Web Full Stack 应用框架. 在 Django 中, ...

  7. android编译架构之添加C项目

    1.  增加一个项目与android编译中枢息息相关.特别需要告诉编译中枢的一些特别信息. 例如: A 这个项目target名字是什么 B 这个项目编译类型是什么,bin?c?lib?or jar? ...

  8. 从国内APP更新“精雕细琢” 看国内外产品理念之差

    看国内外产品理念之差" title="从国内APP更新"精雕细琢" 看国内外产品理念之差"> 对于当下的大众来说,智能手机已经成为新的" ...

  9. VUE实现Studio管理后台(二):Slot实现选项卡tab切换效果,可自由填装内容

    作为RXEditor的主界面,Studio UI要使用大量的选项卡TAB切换,我梦想的TAB切换是可以自由填充内容的.可惜自己不会实现,只好在网上搜索一下,就跟现在你做的一样,看看有没有好事者实现了类 ...

  10. 关于IT培训机构的个人看法

    1.前言 缘分与巧合,最近接触比较多的培训机构出来的人,以及看过关于培训机构的文章和问答.虽然没在培训机构上过课,但是接触过很多培训机构出来的人,也看过一些培训机构的课程.关于培训机构,我也有自己的看 ...