美文网首页Java 杂谈Java
Java语法糖try-with-resources简介及其Sca

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

作者: LittleMagic | 来源:发表于2019-07-30 22:26 被阅读9次

    在我们使用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()方法的异常,所以还是比较好用的。

    相关文章

      网友评论

        本文标题:Java语法糖try-with-resources简介及其Sca

        本文链接:https://www.haomeiwen.com/subject/fseorctx.html