Is it necessary to close each nested OutputStream and Writer separately?

后端 未结 7 2076
醉酒成梦
醉酒成梦 2020-11-28 02:28

I am writing a piece of code:

OutputStream outputStream = new FileOutputStream(createdFile);
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputS         


        
7条回答
  •  时光说笑
    2020-11-28 02:56

    Assuming all the streams get created okay, yes, just closing bw is fine with those stream implementations; but that's a big assumption.

    I'd use try-with-resources (tutorial) so that any issues constructing the subsequent streams that throw exceptions don't leave the previous streams hanging, and so you don't have to rely on the stream implementation having the call to close the underlying stream:

    try (
        OutputStream outputStream = new FileOutputStream(createdFile);
        GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
        OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream);
        BufferedWriter bw = new BufferedWriter(osw)
        ) {
        // ...
    }
    

    Note you no longer call close at all.

    Important note: To have try-with-resources close them, you must assign the streams to variables as you open them, you cannot use nesting. If you use nesting, an exception during construction of one of the later streams (say, GZIPOutputStream) will leave any stream constructed by the nested calls inside it open. From JLS §14.20.3:

    A try-with-resources statement is parameterized with variables (known as resources) that are initialized before execution of the try block and closed automatically, in the reverse order from which they were initialized, after execution of the try block.

    Note the word "variables" (my emphasis).

    E.g., don't do this:

    // DON'T DO THIS
    try (BufferedWriter bw = new BufferedWriter(
            new OutputStreamWriter(
            new GZIPOutputStream(
            new FileOutputStream(createdFile))))) {
        // ...
    }
    

    ...because an exception from the GZIPOutputStream(OutputStream) constructor (which says it may throw IOException, and writes a header to the underlying stream) would leave the FileOutputStream open. Since some resources have constructors that may throw and others don't, it's a good habit to just list them separately.

    We can double-check our interpretation of that JLS section with this program:

    public class Example {
    
        private static class InnerMost implements AutoCloseable {
            public InnerMost() throws Exception {
                System.out.println("Constructing " + this.getClass().getName());
            }
    
            @Override
            public void close() throws Exception {
                System.out.println(this.getClass().getName() + " closed");
            }
        }
    
        private static class Middle implements AutoCloseable {
            private AutoCloseable c;
    
            public Middle(AutoCloseable c) {
                System.out.println("Constructing " + this.getClass().getName());
                this.c = c;
            }
    
            @Override
            public void close() throws Exception {
                System.out.println(this.getClass().getName() + " closed");
                c.close();
            }
        }
    
        private static class OuterMost implements AutoCloseable {
            private AutoCloseable c;
    
            public OuterMost(AutoCloseable c) throws Exception {
                System.out.println("Constructing " + this.getClass().getName());
                throw new Exception(this.getClass().getName() + " failed");
            }
    
            @Override
            public void close() throws Exception {
                System.out.println(this.getClass().getName() + " closed");
                c.close();
            }
        }
    
        public static final void main(String[] args) {
            // DON'T DO THIS
            try (OuterMost om = new OuterMost(
                    new Middle(
                        new InnerMost()
                        )
                    )
                ) {
                System.out.println("In try block");
            }
            catch (Exception e) {
                System.out.println("In catch block");
            }
            finally {
                System.out.println("In finally block");
            }
            System.out.println("At end of main");
        }
    }
    

    ...which has the output:

    Constructing Example$InnerMost
    Constructing Example$Middle
    Constructing Example$OuterMost
    In catch block
    In finally block
    At end of main
    

    Note that there are no calls to close there.

    If we fix main:

    public static final void main(String[] args) {
        try (
            InnerMost im = new InnerMost();
            Middle m = new Middle(im);
            OuterMost om = new OuterMost(m)
            ) {
            System.out.println("In try block");
        }
        catch (Exception e) {
            System.out.println("In catch block");
        }
        finally {
            System.out.println("In finally block");
        }
        System.out.println("At end of main");
    }
    

    then we get the appropriate close calls:

    Constructing Example$InnerMost
    Constructing Example$Middle
    Constructing Example$OuterMost
    Example$Middle closed
    Example$InnerMost closed
    Example$InnerMost closed
    In catch block
    In finally block
    At end of main
    

    (Yes, two calls to InnerMost#close is correct; one is from Middle, the other from try-with-resources.)

提交回复
热议问题