Java 杂谈Java

Java语法糖try-with-resources简介及其Sca

2019-07-30  本文已影响9人  LittleMagic

在我们使用Java编写业务逻辑时,如果打开了一些由外部组件管理的资源(如文件、文件I/O流、数据库连接、网络连接等等),就必须在使用完这些资源之后,通过资源句柄手动关闭。如果不关闭的话,JVM并不会回收它们,就会出现文件被占用无法打开、数据库连接池耗尽等情况。以FileInputStream为例,传统的try-catch-finally写法如下:

public class ResourceCloseSample {
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream("/home/lmagic/1.txt");
            System.out.println(fileInputStream.read());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

看起来真的是过于累赘了。因此从JDK 7开始,提供了一块语法糖,叫做try-with-resources。简化之后,可以写成这样:

public class ResourceCloseSample {
    public static void main(String[] args) {
        try (FileInputStream fileInputStream = new FileInputStream("/home/lmagic/1.txt")) {
             System.out.println(fileInputStream.read());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用try-with-resources的前提是资源句柄(比如上面的FileInputStream对象实例)实现了AutoCloseable接口,我们更熟悉的Closeable接口也是派生自AutoCloseable。这样写可以在资源句柄的作用域结束时自动调用其close()方法,并且仍然支持传统的catch和finally语法,简单方便。

上面使用了try-with-resources的代码反编译之后如下图所示,仍然是try-catch-finally结构,印证了它仅仅是个语法糖。

需要注意的是,一旦try-catch-finally结构中的try语句块与finally语句块都抛出了异常,那么后者在异常传递时会覆盖(抑制)掉前者,前者的异常就消失了。因此,JDK 7也为异常的根Throwable增加了addSuppressed()方法,通过该方法能够将两个异常都记录下来,在使用try-with-resources时也不必单独处理。为了说明它,可以自定义一个只会抛出异常的资源:

public class ResourceCloseSample {
    public static void main(String[] args) {
        try (MyResourceHandle myResourceHandle = new MyResourceHandle()) {
             myResourceHandle.open();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class MyResourceHandle implements AutoCloseable {
    public void open() throws Exception {
        throw new Exception("open() method throws exception");
    }

    @Override
    public void close() throws Exception {
        throw new Exception("close() method throws exception");
    }
}

结果如下。

try-with-resources也能同时使用多个资源,在try后面用分号分隔即可。在使用完后,会先关闭后声明的句柄,后关闭先声明的句柄。以调用HBase的Scan API为例,代码如下:

public class ResourceCloseSample {
    public static void main(String[] args) {
        // 因为创建HBase连接太贵了,所以做成单例
        Connection connection = HBaseConnection.get();
        Scan scan = new Scan().setStartRow(Bytes.toBytes("1")).setStopRow(Bytes.toBytes("7"));

        try (
            Table table = connection.getTable(TableName.valueOf("test_table"));
            ResultScanner scanner = table.getScanner(scan)
        ) {
            for (Result result : scanner) {
                // 处理结果
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

实际上,try-with-resources在Java中出现的已经非常晚了。C++和C#原生就支持在对象生命周期结束时释放资源的方法,前者可以在析构函数中定义,后者可以通过using关键字、IDisposable接口和Dispose()方法实现。但晚出现总比没有好,通过try-with-resources确实能够节省很多不必要的模式性编码,读起来也更简洁。

不过,对于大数据工作者而言,Scala似乎在平时工作中比Java用得多一些(反正我是这样的)。Scala中并不存在try-with-resources语法糖,但这也不妨碍我们自己实现一个。利用泛型、柯里化和高阶函数就可以写出如下方法:

  def tryWithResource[T <: AutoCloseable](handle: T)(func: T => Any): Any = {
    try {
      func(handle)
    } finally {
      if (handle != null) {
        handle.close()
      }
    }
  }

其中,泛型T表示任何继承自AutoCloseable接口的类型,handle表示对应类型的句柄,func是一个函数,代表由句柄进行的操作。然后就可以这样写了:

  def main(args: Array[String]): Unit = {
    tryWithResource(connection.getTable(TableName.valueOf("test_table")) {
      table: Table => {
        tryWithResource(table.getScanner(scan)) {
          scanner => {
            for (result <- scanner) {
              // 处理结果
            }
          }
        }
      }
    })
  }

虽然不及Java的风格来得简便,但大多数情况下都只需要操作一个资源句柄,并且不用处理close()方法的异常,所以还是比较好用的。

上一篇 下一篇

猜你喜欢

热点阅读