Effective Java 3rd 条目9 try-with-resources优于try-finally

Java库包含很多资源,这些资源必须手动调用close方法关闭。这样的例子包括InputStream、OutputStream,和java.sql.Connection。关闭资源常常被客户端忽略,可以预见会有可怕的性能后果。许多这些资源用finalizer作为安全保障,但是finalizer不会很好的工作(条目8)。

历史上,一个资源应该正确地关闭,即使面对一个异常或者返回,try-finally语句是最好的保证方式:

// try-finally - 不再是关闭资源最好的方式!
static String firstLineOfFile(String path) throws IOException { 
    BufferedReader br = new BufferedReader(new FileReader(path)); 
    try { 
        return br.readLine(); 
    } finally { 
        br.close(); 
    } 
}

这可能看上去不太糟,但是当你添加第二个资源时会变得更糟:

// 当用于多于一个资源时,try-finally 是丑陋的! 
static void copy(String src, String dst) throws IOException {       
    try { 
        InputStream in = new FileInputStream(src); 
        try { 
            OutputStream out = new FileOutputStream(dst); 
            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方法的使用,三分之二是错误的。

用try-finally语句关闭资源的正确代码,就像前面两个代码例子,甚至有微妙的缺陷。在两个try代码块和finally代码块中的代码可能会抛出异常。比如,firstLineOfFile方法中,由于底层物体设备的失败,调用readLine可能抛出一个异常,而且close方法调用可能因为同样的原因失败。在这些情形下,第二个异常完全掩盖了第一个。在异常栈信息中没有第一个异常的记录,在实际系统中这个可能使得调试非常复杂,通常为了诊断这个问题,第一个异常是我们想要看见的。为了第一个异常而抑制第二个,虽然编写这样的代码是可能的,事实上,没有人这么干,因为这太啰嗦了。

当Java 7引进了try-with-resource语句[JLS, 14.20.3],这些问题全解决了。为了用在这个结构,一个资源必须实现AutoCloseable接口,这个接口包含一个返回空的close方法。Java库和第三方库中的许多类和接口现在已经实现或者扩展了AutoCloseable。如果你编写一个必须关闭资源的类,你的类也应该实现AutoCloseable。

下面是我们第一个例子使用try-with-resource的样子:

// try-with-resources - 关闭资源的最好方式!
static String firstLineOfFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader( 
            new FileReader(path))) {
        return br.readLine();
    } 
}

下面是我们第二个例子使用try-with-resource的样子:

// 多个资源的try-with-resources - 简短而明了
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]; 
        while ((n = in.read(buf)) >= 0) 
            out.write(buf, 0, n); 
        }
}

不仅try-with-resource版本比原来更加简短和可读,而且它们提供了好得多的可诊断性。考虑firstLineOfFile方法。如果异常由两个readLine调用和(不可见的)close方法抛出,为了前面的异常,后面的抑制了。事实上,为了保护你真实想看到的异常,多个异常可以被抑制。这些抑制的异常没有被丢弃,它们打印在在一个堆栈信息里面,用一个注释说明它们被抑制了。你也可以用getSuppressed方法以编程方式获取它们,在Java 7中这个方法添加到了Throwable。

你可以把catch子句放在try-with-resource语句,就像你可以放在try-finally语句一样。这让你可以处理异常,而没有用另外一层嵌套来污染你的代码。作为有点特别的例子,下面是firstLineOfFile方法的一个版本,它不抛出异常,但是如果不能够打开或者读取文件,它返回一个默认值:

// 有catch子句的try-with-resource 
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-resource,而不是try-finally。这样的代码更加简短和清晰,而且它产生的异常也更加有用。对于使用必须关闭的资源的代码,try-with-resource语句使得正确编写这些代码更容易,而用try-finally是在实践中不可能的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,524评论 18 399
  • 此心光明,亦复何言 Try-with-resources Try-with-resources是Java7出现的一...
    hongXkeX阅读 1,543评论 0 2
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,420评论 0 4
  • 文这才明白,这么多年积攒在硬盘里的电影,都是在等着mo一起来看。时间终将告诉人们,你所做的一切都会是有意义的,只要...
    wen_a阅读 394评论 1 1
  • 今天是传统的女性节日。以前叫妇女节,现在改为女神节、女王节。不管怎么说,都是我们在过节。 自从2012年有了你...
    晓晓丰临阅读 187评论 0 0