Monday, 19 September 2016

Java: close any type in try-with-resources using lambdas

Java 7 introduced the AutoCloseable interface for use in try-with-resources statements. However, not every resource implements this interface. The JDK's own StAX resources don't; 3rd party types like SWT widgets or SNMP4J I/O classes may not be able to for compile-time compatibility reasons.

This post looks at releasing any resource using Java 8 lambdas.

Using method references to auto-close

Implementing AutoCloseable can be a one-liner:

 AutoCloseable closer = reader::close

However this means the base Exception type needs to be dealt with:

  public static void consume(XMLInputFactory factory, Source source, Consumer<XMLEvent> eventConsumer)
      throws XMLStreamException {
    XMLEventReader reader = factory.createXMLEventReader(source);
    try (AutoCloseable closer = reader::close) {
      while (reader.hasNext()) {
        eventConsumer.accept(reader.nextEvent());
      }
    } catch (RuntimeException e) {
      throw e;
    } catch (XMLStreamException e) {
      throw e;
    } catch (Exception e) {
      throw new AssertionError("Not going to happen");
    }
  }

Better auto-close with method references

The problem of dealing with Exception can be dealt with by defining a more specific interface:

  public static void consume(XMLInputFactory factory, Source source, Consumer<XMLEvent> eventConsumer)
      throws XMLStreamException {
    XMLEventReader reader = factory.createXMLEventReader(source);

    try (XMLStreamCloser closer = reader::close) {

      while (reader.hasNext()) {
        eventConsumer.accept(reader.nextEvent());
      }
    }
  }

  private interface XMLStreamCloser extends AutoCloseable {
    void close() throws XMLStreamException;
  }

However if XMLStreamException needs to be handled within the method you start to deal with nested try blocks:

  public static void consume(XMLInputFactory factory, Source source, Consumer<XMLEvent> eventConsumer) {
    try {
      XMLEventReader reader = factory.createXMLEventReader(source);

      try (XMLStreamCloser closer = reader::close) {

        while (reader.hasNext()) {
          eventConsumer.accept(reader.nextEvent());
        }
      }
    } catch (XMLStreamException e) {
      // TODO: handle exception
    }
  }

  private interface XMLStreamCloser extends AutoCloseable {
    void close() throws XMLStreamException;
  }

It gets worse when there is more than one resource:

  public static void copy(XMLInputFactory inFactory, Source source, XMLOutputFactory outFactory, Result result) {
    try {
      XMLEventReader reader = inFactory.createXMLEventReader(source);

      try (XMLStreamCloser closer = reader::close) {

        XMLEventWriter writer = outFactory.createXMLEventWriter(result);

        try (XMLStreamCloser writerCloser = writer::close) {

          while (reader.hasNext()) {
            writer.add(reader.nextEvent());
          }
        }
      }
    } catch (XMLStreamException e) {
      // TODO: handle exception
    }
  }

  private interface XMLStreamCloser extends AutoCloseable {
    void close() throws XMLStreamException;
  }

Auto-close any type

Lambdas and method references can be used to write more compact code at the expense of making the try statement uglier:

//import uk.kludje.Res;
//import static uk.kludje.Res.res;

  public static void copy(XMLInputFactory inFactory, Source source, XMLOutputFactory outFactory, Result result) {

    try (Res<XMLEventReader> reader = res(XMLEventReader::close, inFactory.createXMLEventReader(source));
        Res<XMLEventWriter> writer = res(XMLEventWriter::close, outFactory.createXMLEventWriter(result))) {

      while (reader.unwrap().hasNext()) {
        writer.unwrap().add(reader.unwrap().nextEvent());
      }
    } catch (XMLStreamException e) {
      // TODO: handle exception
    }
  }

This code leverages the KludJe Res (doc) class as an example.

Resources

See:

No comments:

Post a Comment

All comments are moderated