Search code examples
javatry-with-resourcestry-with

Inheritance and Try-With-Resources


Suppose there are two classes implementing AutoCloseable Interface as below:

public class Closing1 implements AutoCloseable {

private boolean closed;

@Override
public void close() throws Exception {
    if (closed) {
        throw new Exception("Closed Already");
    }
    this.closed = true;
    System.out.println("Closing1 closed");
}

public boolean isClosed() {
    return closed;
}

}

and

public class Closing2 implements AutoCloseable {

private Closing1 cl1;

public Closing2(Closing1 cl1) {
    this.cl1 = cl1;
}

@Override
public void close() throws Exception {
    if(!cl1.isClosed()) {
        throw new Exception("Closing1 not closed");
    }
    System.out.println("Closing2 closed");
}

}

I find that all variations with try with resources lead to an exception! Is there something I am missing here, or is it just the way TWR is designed?

        try(Closing1 c1 = new Closing1();Closing2 c2 = new Closing2(c1)){
            System.out.println("Done");
        } //Exception while auto closing C2

or

        try(Closing1 c1 = new Closing1();Closing2 c2 = new Closing2(c1)){
            System.out.println("Done");
            c1.close();
        } // exception while auto closing c1

Solution

  • Start with try-with-resources first, https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
    As the very first example shows already:

    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
    

    people will not necessarily name everything in the chain.

    Unless you explicitly need c1 for something (other than closing), in real life your snippet would rather look like

    try(Closing2 c2 = new Closing2(new Closing1())){
        System.out.println("Done");
    }
    

    and you would not call c1.close() in the try-block for sure, as there would be no c1 at all.

    Keeping this in mind, throwing an exception from c2 because the contained c1 is not closed, is totally wrong, actually c2 owns the Closing1 object and should invoke close() on it:

    class Close1 implements AutoCloseable {
        @Override
        public void close() throws Exception {
            System.out.println("Closing c1");
        }
    }
    
    class Close2 implements AutoCloseable {
        Close1 c1;
        Close2(Close1 c1) {
            this.c1=c1;
        }
    
        @Override
        public void close() throws Exception {
            System.out.print("Closing c1 from c2: ");
            c1.close();
            System.out.println("Closing c2");
        }
    }
    
    void test() {
        System.out.println("Before try block");
        try(Close2 c2=new Close2(new Close1())) {
            System.out.println("In try block");
        }
        catch(Exception ex) {
            System.out.println("Exception: "+ex);
        }
        finally {
            System.out.println("In finally block");
        }
        System.out.println("After try block");
    }
    

    However, if someone gives a name to c1, it will be closed twice, that is where the idempotency comes into the picture, as suggested by someone already:

    System.out.println("Before try block");
    try(Close1 c1 = new Close1(); Close2 c2 = new Close2(c1)){
        System.out.println("In try block");
    }
    catch(Exception ex){
        System.out.println("Exception: "+ex);
    }
    finally{
        System.out.println("In finally block");
    }
    System.out.println("After try block");
    

    As BufferedReader was mentioned already, this is the close() method it has:

    public void close() throws IOException {
        synchronized (lock) {
            if (in == null)
                return;
            try {
                in.close();
            } finally {
                in = null;
                cb = null;
            }
        }
    }
    

    If it has in, it gets closed, and nulled (in a finally block, so it happens even if an exception occurs), and all in a thread-safe block. (cb is just an array of characters, it gets null-ed too, simplifying the life of the garbage collector a little). Because of nulling everything in the finally block, any extra calls to this same method will not do anything (apart from synchronizing on the lock for a moment).