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

1

10

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.

Pennywise answered 12/10, 2019 at 15:35 Comment(4)
This seems quite clever. Great question.Iambus
The Java Language Specification has a translation (equivalent code) for it: docs.oracle.com/javase/specs/jls/se13/html/…Hereford
Could be written as final AutoCloseable dummy = context::closeUnscrupulous
"Thus, I am suspicious it may not be correct." I would honestly be more suspicious about the correctness of the first code. I think the second is more self-evidently correct, because it's just simpler.Nitrosyl
B
1

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:

  • Both variant1 and variant2 come at the cost of dummy objects which inside the try block you will never use, unless you cast them to InitialDirContext first. Instead, you could directly use the outer context objects, of course, which is also what you suggested.
  • In variant3 the auto-closable context object directly has the correct (sub-)type, so you can actually work with it seamlessly without casting or dummy object. This comes at the cost of a special subclass.
Bonin answered 2/1, 2021 at 2:49 Comment(2)
Update: I made my sample code do something meaningful (an actual DNS lookup).Bonin
Sorry, I took so long to accept this answer. It is very detailed! The best answers on SO will give a variety of possibilities and help the reader the narrow down the best. Cheers.Pennywise

© 2022 - 2024 — McMap. All rights reserved.