创建销毁对象(第九条:比起TRY – FINALLY要更喜欢TRY -WITH-RESOURCES)

Java类库里面包含很多必须通过调用close方法手动关闭的资源。比如InputStream,OutputStream,和java.sql.Connection。关闭资源经常被客户端所忽视,情理之中也就导致了一些可怕的性能问题。尽管很多资源使用finalizers作为安全保障,但是finalizers却工作的不太好(Item 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();
        } finally {
            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);
            } finally {
                out.close();
            }
        } finally {
            in.close();
        }
    }


很多时候优秀的开发人员也会犯这种错,这可能很难相信。一开始,我把Java Puzzlers第88页弄错了,好几年都没人发现。事实上,在2007年Java类库中三分之二的close方法的使用都是错的。

甚至对于正确的使用try – finally去关闭资源的语句,比如上面的两个例子,都是存在隐含的缺陷的。在try代码块和finally代码块里面的代码是能够抛出异常的。比如,在firstLineOfFile方法里面,由于基础的物理设备的故障,调用readLine方法就会抛出一个异常。那么调用close就会因为相同原因也失败掉。在这种情况下,第二个异常就完全把第一个给抹去了。在异常栈追踪里面没有第一个异常,这样会在真实系统中极大地增加调试的复杂性,尤其在你想看第一个异常来诊断异常的时候。尽管可以写代码来阻止第二个异常抛出来以达到让第一个异常显示,但是事实上却没人这么做,因为代码太繁琐了。

当java 7引入了try -with-resources语句后,所有的这些问题一下子都被解决了。为了使用这种结构,资源必须实现AutoCloseable接口,这个接口仅仅由一个返回值为void的close方法组成。在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的调用都抛出了异常,那么为了支持前者后面的异常就被吃掉了。事实上,为了维护你真实想看的异常,多个异常可能都被吃掉了。这种被吃掉的异常也不是直接丢掉了;它们带着一个标记被打印在堆栈里面,这个标记会表明它们是被抑制了的。你也可以通过程序中的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-finally要总是更喜欢使用try -with-resources。try -with-resources的代码更短更清晰,它产生的异常也更有用。try- with-resources语句让写必须关闭资源的正确的代码变得简单,这一点使用try – finally实际上是不可能的。
阅读更多
个人分类: Effective Java 3
上一篇创建销毁对象(第八条:杜绝使用FINALIZERS和CLEANERS)
下一篇对象共有的方法(第十条:当重写EQUALS的时候要遵守基本约定)
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭