如何在try代码块中合理地关闭资源

一提到try-catch-finally代码块,我们都被这三者中代码执行顺序、异常抛出处理结果以及资源关闭所折磨,今天主要来讲讲finally中资源关闭的使用。

教科书都告诉我们,finally中建议只写和资源关闭回收相关的代码,不要写和业务相关的逻辑,因为它们执行效率很低,还面临上述执行顺序不明的风险。即便如此,很多人还是不能在finally中正确合理地关闭资源。

一、错误地关闭方式

我们创建一个maven工程,然后在src/main/resources目录下新建一个test.properties,其中内容如下:

name=zhang
age=26
gender=male

然后我们写一个程序是想读取这个配置文件中的所有配置项并打印出来。

先来看看如下的错误关闭资源方式一:

public class StreamTest1 {

    private static Logger log = LoggerFactory.getLogger(StreamTest1.class);

    public static void main(String[] args) {
        String fileName = "src/main/resources/test.properties";
        Properties props = new Properties();
        InputStream inputStream = null;

        try {
            inputStream = new FileInputStream(fileName);
            props.load(inputStream);
            // 一旦上面两行代码抛出异常,inputStream就不会被关闭
            inputStream.close();
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }

        for (Map.Entry entry : props.entrySet()) {
            log.info("key:{},value:{}", entry.getKey().toString(), entry.getValue().toString());
        }
    }

}

如上代码的资源关闭写在了try代码块中,一旦close方法调用之前就抛出异常,那么关闭资源的代码就永远不会得到执行。

那我们把关闭资源的代码放在finally中就一定正确了吗?

        try {
            inputStream = new FileInputStream(fileName);
            props.load(inputStream);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }

看起来似乎没有问题。但是,倘若在try中inputStream读取文件抛出异常,那么inputStream仍然为null,此时进入finally代码块中,执行close的意义是什么呢?因此,我们需要在close之前判断一下inputStream是否为空,只有不为空的时候才需要close。

二、 旧风格的关闭方式

看过了上述错误使用场景,那么正确的使用关闭方式大致如下:

public class StreamTest3 {

    private static Logger log = LoggerFactory.getLogger(StreamTest3.class);

    public static void main(String[] args) {
        String fileName = "src/main/resources/test.properties";
        Properties props = new Properties();
        InputStream inputStream = null;

        try {
            inputStream = new FileInputStream(fileName);
            props.load(inputStream);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        } finally {
            // 如果inputStream本身为空就不需要再进行关闭操作
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }

        for (Map.Entry entry : props.entrySet()) {
            log.info("key:{},value:{}", entry.getKey().toString(), entry.getValue().toString());
        }
    }

}

However,始终感觉这么做太繁琐了,这还只是一个资源的关闭,倘若有三个资源需要在finally中关闭,岂不是徒增很多不优雅的代码?反过来想想,资源的关闭其实是没啥逻辑的代码,就像垃圾回收和资源分配一样,为啥要让程序员显示地来操作呢?

三、使用try-with-resources了

自从Java7以后,程序员就被从资源的手动关闭中解放了,使用try-with-resources来改写以上的例子:

public class StreamTest4 {

    private static Logger log = LoggerFactory.getLogger(StreamTest4.class);

    public static void main(String[] args) {
        String fileName = "src/main/resources/test.properties";
        Properties props = new Properties();

        // 使用try-with-resource,自动关闭需要close的资源
        try (
                InputStream inputStream = new FileInputStream(fileName);
        ) {
            props.load(inputStream);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }

        for (Map.Entry entry : props.entrySet()) {
            log.info("key:{},value:{}", entry.getKey().toString(), entry.getValue().toString());
        }
    }

}

如此,丑陋繁琐的finally就不是必要的了。注意,我们说的是finally中的关闭资源代码可以不必要了,但是finally代码块如果有的话,还是可以执行的。

四、try-with-resources使用场景

当然了,并不是所有的资源都是可以使用try-with-resources来进行管理的,只有实现了java.lang.AutoCloseable接口的,并且实现了close方法的资源类才能如此使用。

实现AutoCloseable接口

除此之外,我们可以自定义资源类来支持使用try-with-resources。

public class MyAutoClosable implements AutoCloseable {
    private Logger log = LoggerFactory.getLogger(MyAutoClosable.class);
    @Override
    public void close() {
        log.info("MyAutoClosable closed!");
    }
    public void print() {
        log.info("MyAutoClosable printed!");
    }
}
public class StreamTest5 {
    private static Logger log = LoggerFactory.getLogger(StreamTest5.class);
    public static void main(String[] args) {
        // 使用try-with-resource,自动关闭需要close的资源
        try (
                MyAutoClosable mac = new MyAutoClosable();
        ) {
            mac.print();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            log.info("finally!");
        }
    }
}

执行结果如下:

MyAutoClosable printed!
MyAutoClosable closed!
finally!

你可能感兴趣的:(如何在try代码块中合理地关闭资源)