高效Java编程-09. 使用try-with-resources语句替代try-finally语句

  Java 类库中包含许多必须通过调用 close 方法手动关闭的资源。 比如 InputStreamOutputStreamjava.sql.Connection。 客户经常忽视关闭资源,其性能结果可想而知。 尽管这些资源中有很多使用 finalizer 机制作为安全网,但 finalizer 机制却不能很好地工作(详见第 8 条)。

  从以往来看,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();
    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);
    out.close();
    }
    in.close();
    }
   }

  这可能很难相信,但即使是优秀的程序员,大多数时候也会犯错误。首先,我在 Java Puzzlers[Bloch05] 的第 88 页上弄错了,多年来没有人注意到。事实上,2007 年 Java 类库中使用 close 方法的三分之二都是错误的。

  即使是用 try-finally 语句关闭资源的正确代码,如前面两个代码示例所示,也有一个微妙的缺陷。 try-with-resources 块和 finally 块中的代码都可以抛出异常。 例如,在 firstLineOfFile 方法中,由于底层物理设备发生故障,对 readLine 方法的调用可能会引发异常,并且由于相同的原因,调用 close 方法可能会失败。 在这种情况下,第二个异常完全冲掉了第一个异常。 在异常堆栈跟踪中没有第一个异常的记录,这可能使实际系统中的调试非常复杂——通常这是你想要诊断问题的第一个异常。 虽然可以编写代码来抑制第二个异常,但是实际上没有人这样做,因为它太冗长了。

  当 Java 7 引入了 try-with-resources 语句时,所有这些问题一下子都得到了解决[JLS,14.20.3]。要使用这个构造,资源必须实现 AutoCloseable 接口,该接口由一个返回为 voidclose 组成。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 方法都抛出异常,则后一个异常将被抑制(suppressed),而不是前者。 事实上,为了保留你真正想看到的异常,可能会抑制多个异常。 这些抑制的异常没有被抛弃, 而是打印在堆栈跟踪中,并标注为被抑制了。 你也可以使用 getSuppressed 方法以编程方式访问它们,该方法在 Java 7 中已添加到的 Throwable 中。

  可以在 try-with-resources 语句中添加 catch 子句,就像在常规的 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 语句实际上是不可能的。

文章列表

更多推荐

更多
  • Spark编程-结构化流式编程指南 概述,简单例子,编程模型,使用 Dataset 和 DataFrame 的API,连续处理,额外信息,基本概念,处理 Eventtime 和 Late Data,faulttolerance 语义,创建流式 DataFrame 和流式
  • Spark编程-20 Spark 配置Spark 属性,Environment Variables环境变量,Configuring Logging配置 Logging,Overriding configuration directory覆盖配置目录,Inhe
  • Spark编程-在Mesos上运行Spark 运行原理,安装 Mesos,连接 Spark 到 Mesos,Mesos 运行模式,Mesos Docker 支持,集成 Hadoop 运行,使用 Mesos 动态分配资源,配置,故障排查和调试,从源码安装,第三方软件包,验证,上传 S
  • Spark编程-Running Spark on YARN 启动 Spark on YARN,准备,配置,调试应用,在安全集群中运行,添加其他的 JARs,配置外部的 Shuffle Service,用 Apache Oozie 来运行应用程序,Kerberos 故障排查,使用 Spark Hi
  • Spark编程-Spark 调优 数据序列化,内存调优,其它考虑,,内存管理概论,确定内存消耗,优化数据结构,序列化 RDD 存储,GC优化,并行级别,Reduce任务内存使用,广播大变量,数据局部性, 由于大多数Spark计算都在内存中,所以集群中的任何资源(C
  • Spark编程-Spark Standalone模式 安装 Spark Standalone 集群,手动启动一个集群,集群启动脚本,提交应用程序到集群中,启动 Spark 应用程序,Resource Scheduling资源调度,监控和日志,与 Hadoop 集成,配置网络安全端口,高可用
  • Spark编程-Monitoring and Instrumentation Web 界面,Metrics,高级工具,事后查看,REST API,环境变量,Spark配置选项,API 版本控制策略, 有几种方法来监视 Spark 应用程序:Web UI,metrics 和外部工具。 Web 界面每
  • Spark编程-Spark提交任务Submitting Applications 打包应用依赖,用 sparksubmit 启动应用,Master URLs,从文件中加载配置,高级的依赖管理,更多信息, 在 script in Spark的 `bin` 目录中的`spark-submit` 脚本用与在集群上启动
  • Spark编程-作业调度 概述,跨应用调度,应用内调度,动态资源分配,公平调度资源池,资源池默认行为,配置资源池属性,配置和部署,资源分配策略,优雅的关闭Executor(执行器),概述Spark 有好几计算资源调度的方式。首先,回忆一下 [集群
  • Spark编程-Spark 概述 安全,下载,运行示例和 Shell,在集群上运行,进一步学习链接, Apache Spark 是一个快速的,通用的集群计算系统。它对 Java,Scala,Python 和 R 提供了的高层 API,并有一个经优化的支持通用执行图
  • 近期文章

    更多
    文章目录

      推荐作者

      更多