Friday, March 13, 2015

There can be only one

Highlander adds the missing "only" operation to Java.

A common programming task is to find a single match from a collection of possibilities, for example finding exactly one person by email address from a collection of people. This may sounds simple, but there are complexities in doing so correctly and succinctly.

A naive solution to the above problem might be written in Java 7 as:

However this code exhibits two related problems:
  • If there is no match, null is returned. This may result in a null pointer exception being thrown later in the code. (Note that if the requirement is that no match as a valid case, we should return Optional<Person>, discussed briefly later in this article).

  • If there are multiple matches, the first is returned - this will result in the wrong result being used later in the code.
  • In both cases it would be desirable to "fail fast", in other words throw an exception immediately when an assumption is violated. Updating the code to handle these two cases results in:

    We now have code that behaves correctly, but given how common this problem is, can we find a more compact way to write this? If we are able to use Java 8 we could consider Streams and lambda expressions:

    This is certainly more succinct, and the findFirst().get() line will throw a NoSuchElementException if there are no matches - but still incorrectly returns the first match if there are multiple matches. As it turns out, there is no built in construct in the Streams library that will return the only element with fail-fast behaviour if there are multiple elements.

    So how do we solve this problem? One answer to write a static generic utility method, perhaps named "only", that encapsulates the desired logic - but we will soon discover there are three different language constructs in Java we will want to support:
    • Arrays
    • Iterables (the more general case of Collections)
    • Streams
    Also, we would also want to cater for requirements that no match being a valid case by returning an Optional.

    Lastly, we would want to support both Java 7 and 8, and both the Guava Optional in Java 7 and the inbuilt Optional in Java 8.

    This is the problem Highlander solves - a micro-library that allows retrieval of the only or optional only element in arrays, iterables/collections and streams by providing static "only" methods that cover all these cases. Using this, we can solve the original problem correctly and succinctly - in Java 7 we might do this using the Guava filter method:

    and in Java 8 the code can be reduced to a single line without sacrificing readability or correctness by using the convenience form of "only" that accepts a collection and predicate lambda:

    3 comments:

    1. In Java 8, using protonpack (http://github.com/poetix/protonpack):

      assertThat(Stream.of(1, 2, 3).filter(i -> i > 3).collect(unique()), equalTo(Optional.empty()));
      assertThat(Stream.of(1, 2, 3).filter(i -> i > 2).collect(unique()), equalTo(Optional.of(3)));

      // Throws NonUniqueValueException
      Stream.of(1, 2, 3).filter(i -> i > 1).collect(unique());

      ReplyDelete
      Replies
      1. Hi Dominic - Thanks for adding unique() to protonpack, thats another useful option.

        Delete