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.
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:
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.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.