Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 632
kevinarpe Avatar asked Oct 12 '19 15:10

kevinarpe


People also ask

Which of the following interface should be implemented by a resource if we want to create the object of that resource in the resource part of a try block?

All objects managed by a try with resources statement must implement the AutoCloseable interface. Multiple AutoCloseable objects can be created within Java's try with resources block.

What is AutoCloseable interface in Java?

public interface AutoCloseable. An object that may hold resources (such as file or socket handles) until it is closed. The close() method of an AutoCloseable object is called automatically when exiting a try -with-resources block for which the object has been declared in the resource specification header.

How do you signal to Java that an object can be used in a try with resources block?

The try -with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try -with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.

What is try () in Java?

The try statement allows you to define a block of code to be tested for errors while it is being executed. The catch statement allows you to define a block of code to be executed, if an error occurs in the try block.


1 Answers

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.
like image 65
kriegaex Avatar answered Oct 26 '22 00:10

kriegaex