javatry-catch-finallytry-with-resourcesautocloseable

Is using a lambda a safe, correct, and equivalent workaround for classes that do not implement AutoCloseable?


Background: I use the Java class InitialDirContext to access LDAP directories. Unfortunately, it does not implement interface AutoCloseable, so it cannot be used in try-with-resources blocks.

Here is the original code I wrote: (inspired by this answer)

final Properties props = new Properties();
// Populate 'props' here.
final InitialDirContext context = new InitialDirContext(props);
Exception e0 = null;
try {
    // use 'context' here
}
catch (Exception e) {
    // Only save a reference to the exception.
    e0 = e;
    // Why re-throw?
    // If finally block does not throw, this exception must be thrown.
    throw e;
}
finally {
    try {
        context.close();
    }
    catch (Exception e2) {
        if (null != e0) {
            e0.addSuppressed(e2);
            // No need to re-throw 'e0' here.  It was (re-)thrown above.
        }
        else {
            throw e2;
        }
    }
}

Is this a safe, correct, and equivalent replacement?

try (final AutoCloseable dummy = () -> context.close()) {
    // use 'context' here
}

I think the answer is yes, but I want to confirm. I tried Googling for this pattern, but I found nothing. It is so simple! Thus, I am suspicious it may not be correct.

Edit: I just found this answer with a similar pattern.


Solution

  • As explained in the other answer you linked to, it is not strictly equivalent because you have to either catch or throw Exception from AutoCloseable.close() and you must be sure not to do anything with context after the try block because it is not out of scope as if InitialDirContext directly implemented AutoCloseable. Still I agree with others that this workaround is quite nice.

    Of course, you could also extend InitialDirContext and make it implement AutoCloseable directly or (for final classes) use a delegator pattern and wrap the target object.

    package de.scrum_master.stackoverflow;
    
    import javax.naming.NamingException;
    import javax.naming.directory.InitialDirContext;
    import java.util.Hashtable;
    import java.util.Properties;
    
    public class TryWithResourcesAutoCloseableWrapper {
    
      public static void main(String[] args) throws NamingException {
        final Properties props = new Properties();
        props.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
        variant1(props);
        variant2(props);
        variant3(props);
      }
    
      public static void variant1(Properties props) throws NamingException {
        final InitialDirContext context = new InitialDirContext(props);
        try (final AutoCloseable dummy = context::close) {
          lookupMX(context);
        }
        catch (NamingException ne) {
          throw ne;
        }
        catch (Exception e) {
          e.printStackTrace();
        }
      }
    
      public static void variant2(Properties props) throws NamingException {
        final InitialDirContext context = new InitialDirContext(props);
        try (final MyCloseable dummy = context::close) {
          lookupMX(context);
        }
      }
    
      public static void variant3(Properties props) throws NamingException {
        try (final MyInitialDirContext context = new MyInitialDirContext(props)) {
          lookupMX(context);
        }
      }
    
      private static void lookupMX(InitialDirContext context) throws NamingException {
        System.out.println(context.getAttributes("scrum-master.de", new String[] { "MX" }));
      }
    
      public interface MyCloseable extends AutoCloseable {
        void close() throws NamingException;
      }
    
      public static class MyInitialDirContext extends InitialDirContext implements AutoCloseable {
        public MyInitialDirContext(Hashtable<?, ?> environment) throws NamingException {
          super(environment);
        }
      }
    
    }
    

    A few more thoughts about how to use these workarounds: