《Effective Java》学习笔记9 Prefer try-with-resources to try-finally

版权声明:欢迎转载,但麻烦注明出处 https://blog.csdn.net/q2878948/article/details/81331480

本栏是博主根据如题教材进行Java进阶时所记的笔记,包括对原著的概括、理解,教材代码的报错和运行情况。十分建议看过原著遇到费解地方再来参考或与博主讨论。致敬作者Joshua Bloch和各路翻译者们,以及为我提供可参考博文的博主们。

 try-with-resources代替try-finally

许多资源使用完毕后需要用CLOSE方法手动关闭,例如InputStream、OutputStream和java.sql.Connection。释放资源通常不被重视,这样可能会造成很多严重的问题。虽然这些资源很多会用finalizer()方法保证一下被安全释放,但finalizer()的一些缺陷可能并不那么让人满意(见item 8)。

Java7之前,一般会用try-finally处理有关资源释放的问题,保证即使出现异常,资源也会在finally中被释放,比如下面的例子:

import java.io.*;

/**
 * 这个类用来说明Java7之前保证共享资源一定会被释放的方式。但里面几种方法可读性、正确性方面多少存在问题。
 *
 * 在Java7之后,这些问题也都随着try-with-resources的出现而得到解决{@link TryWithResourcesTest},
 * 比如在IDEA中,自动的代码提示也会建议开发者用这种方式。
 *
 * @author LightDance
 */
public class TryFinallyTest {

    private static final int BUFFER_SIZE = 1024;

    /**这种方式已不是释放资源最优雅的方案了*/
    static String firstLineOfFile(String path) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(path));
        try {
            return br.readLine();
        } finally {
            br.close();
        }
    }

    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();
        }
    }
    static void firstLineOfFile2(String path) {
        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream(new File(path));
            System.out.println(inputStream.read());
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e.getMessage(), e);
                }
            }
        }
    }
}

但其问题也比较明显,firstLineOfFile(String)只涉及到一种资源的释放。但如果再加一种,就比较丑陋了{@link #copy(String, String)},这种涉及到两项及以上资源关闭的方法,有时候会不得不使用嵌套try-finally,代码可读性一下子就拉下来了。

此外,更重要的是,有时候finally中的语句也可能会抛出异常。比如{@link #firstLineOfFile(String)}中,br.close()也有可能会有异常抛出来,于是有了经典的firstLineOfFile2(String)

并且,由于底层物理设备可能会失效,br.readLine()和br.close()两者抛出异常的原因又相同,那么就非常可能给debug工作带来困难。我们通常想捕获的是前一个异常,但却无法进行区分。虽然通过代码能忽略其一来寻找异常源,可是实际上这样做的人很少,因为麻烦而且代码比较冗长。

幸好,在Java7之后,这些问题也都随着try-with-resources的出现而得到解决。

Try-With-Resources

Java7之后,try-with-resources语句应运而生,其实说白了try-with-resources就是给了个语法糖,反编译.class文件后看到的还是TryFinallyTest.firstLineOfFile2(String)的形式,但是.java文件中可读性果然要高很多。并且,反编译后会有这么一行:var2.addSuppressed(var10);,这样就把在finally中抛出的异常抑制住了,不过如果我们像查看的话还是可以通过Throwable.getSuppressed()去查看它。

使用的话,资源类需要implements一下{@link AutoCloseable}接口,然后就能用这种简单的方式了,比较简单,比如这个{@link MyResource},然后直接在try后面的括号中添加需要保证被关闭的资源声明语句。

/**
 * 虚拟一种资源,以配合try-with-resources的使用,这种资源需要implements一下
 * {@link AutoCloseable}接口
 */
public class MyResource implements AutoCloseable{
    @Override
    public void close() throws Exception {
        //release resources here
    }

    public void useRes(){
        System.out.println("哈罗");
    }
}

现在Java自带的很多类已经修改为直接或间接地继承这个接口了,可以放心使用,比如{@link java.io.OutputStream},{@link java.io.InputStream},{@link java.io.BufferedReader}等。

对比下面这个copy(String, String)和之前的TryFinallyTest.copy(String, String)},可读性优势显而易见

public class TryWithResourcesTest {

    private static final int BUFFER_SIZE = 1024;

    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);
            }
        }
    }

    static String firstLineOfFile(String path, String defaultVal) {
        try (BufferedReader br = new BufferedReader(
                new FileReader(path))) {
            return br.readLine();
        } catch (IOException e) {
            return defaultVal;
        }
    }

    public static void main(String[] args) throws Exception {
        try(MyResource resource = new MyResource()){
            resource.useRes();
        }
    }
}

同样,try-with-resources也可以设置捕获异常后的处理方案,比如firstLineOfFile(String, String),可以在出现io异常时返回默认字符串,然后根据.close()方法释放资源。

全代码git地址:点我点我

猜你喜欢

转载自blog.csdn.net/q2878948/article/details/81331480