Wednesday, July 8, 2015

The wonderful world of Java 8's default interface methods

For those of you with experience in Scala, Java 8's default methods in interfaces are no big news. For Java-only folks my guess is, that some may have wondered, why an interface - in its essence a contract between a client and a library - would need to contain implementation details.

I think one of the greatest benefits of default methods is, that functionality can be composed through implementing interfaces, thereby using them as mixins or traits as they are called in Scala.

Today, i would like to share an exemplary use case of default methods when working with tree structures.

Suppose you have the following two classes somewhere in your project...one might be used for the internal representation of your binary-tree data, the other one is used as a DTO for a REST API and also needs to be able to handle non-binary trees.

public class BinaryTree {  
    private final String name;
    private final BinaryTree left;
    private final BinaryTree right;

    public BinaryTree(final String name, final BinaryTree left, final BinaryTree right) {
        this.name = name;
        this.left = left;
        this.right = right;
    }

    public String getName() {
        return name;
    }

    public BinaryTree getLeft() {
        return left;
    }

    public BinaryTree getRight() {
        return right;
    }
}
public class SomeTree {  
    private final String name;
    private final Set<SomeTree> children;

    public SomeTree(final String name, final Set<SomeTree> children) {
        this.name = name;
        this.children = children;
    }

    public String getName() {
        return name;
    }

    public Set<SomeTree> getChildren() {
        return children;
    }
}

Many times when working with tree structures, we need to be able to find nodes somehow. So usually we implement some methods to recursively or iteratively locate the node in question and return it. Since our two tree representations are structurally different, we might think that we need to write a separate method for finding nodes for each of our two types.

Fortunately, we can use Java 8's default methods in interfaces to write just one implementation and mix the interface into both our classes. Note, that this could also have been done using an abstract base class for both our tree classes, but that would mean tighter coupling and worse maintainability, since any class in java can only extend one other class, but implement multiple interfaces.

Thus we continue to create a generic implementation enabling us to find nodes in a tree:

public interface FindableNode {

    String getName();

    <T extends FindableNode> Set<T> getChildren();

    default <T extends FindableNode> Optional<T> findByName(final String name) {
        if (getName().equalsIgnoreCase(name)) {
            return Optional.of((T) this);
        }
        return findByName(getChildren(), name);
    }

    default <T extends FindableNode> Optional<T> findByName(final Set<T> nodes, final String name) {
        final Optional<T> matchingNode = nodes.stream()
                .filter(node -> node.getName().equalsIgnoreCase(name))
                .findAny();
        if (matchingNode.isPresent()) {
            return matchingNode;
        }
        return (Optional<T>) nodes.stream()
                .map(c -> findByName(c.getChildren(), name))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .findFirst();
    }
}

Now all we need to do is to mix our new interface into our two tree classes and provide the required abstract methods getName() and getChildren():

public class BinaryTree implements FindableNode {  
    private final String name;
    private final BinaryTree left;
    private final BinaryTree right;

    public BinaryTree(final String name, final BinaryTree left, final BinaryTree right) {
        this.name = name;
        this.left = left;
        this.right = right;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public Set<BinaryTree> getChildren() {
        final ImmutableSet.Builder<BinaryTree> builder = ImmutableSet.<BinaryTree>builder();
        if (left != null) {
            builder.add(left);
        }
        if (right != null) {
            builder.add(right);
        }
        return builder.build();
    }


    public BinaryTree getLeft() {
        return left;
    }

    public BinaryTree getRight() {
        return right;
    }
}

public class SomeTree implements FindableNode {  
    private final String name;
    private final Set<SomeTree> children;

    public SomeTree(final String name, final Set<SomeTree> children) {
        this.name = name;
        this.children = children;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public Set<SomeTree> getChildren() {
        return children;
    }
}

We can check if our construct does what we expect using a Unit Test:

public class FindableNodeTest {

    @Test
    public void testFindBranchByNameWithSomeTree() {
        final Optional<SomeTree> result = createSomeTree().findByName("branch2");
        assertThat(result.isPresent(), is(true));
        assertThat(result.get().getName(), is("branch2"));
        assertThat(result.get().getChildren().size(), is(2));
    }

    @Test
    public void testFindLeafByNameWithSomeTree() {
        final Optional<SomeTree> result = createSomeTree().findByName("leaf3");
        assertThat(result.isPresent(), is(true));
        assertThat(result.get().getName(), is("leaf3"));
        assertThat(result.get().getChildren().size(), is(0));
    }

    @Test
    public void testFindBranchByNameWithBinaryTree() {
        final Optional<BinaryTree> result = createBinaryTree().findByName("branch2");
        assertThat(result.isPresent(), is(true));
        assertThat(result.get().getName(), is("branch2"));
        assertThat(result.get().getChildren().size(), is(2));
    }

    @Test
    public void testFindLeafByNameWithBinaryTree() {
        final Optional<BinaryTree> result = createBinaryTree().findByName("leaf3");
        assertThat(result.isPresent(), is(true));
        assertThat(result.get().getName(), is("leaf3"));
        assertThat(result.get().getChildren().size(), is(0));
    }

    private SomeTree createSomeTree() {
        final SomeTree leaf1 = new SomeTree("leaf1", ImmutableSet.of());
        final SomeTree leaf2 = new SomeTree("leaf2", ImmutableSet.of());
        final SomeTree leaf3 = new SomeTree("leaf3", ImmutableSet.of());
        final SomeTree leaf4 = new SomeTree("leaf4", ImmutableSet.of());
        final SomeTree branch1 = new SomeTree("branch1", ImmutableSet.of(leaf1, leaf2));
        final SomeTree branch2 = new SomeTree("branch2", ImmutableSet.of(leaf3, leaf4));
        return new SomeTree("root", ImmutableSet.of(branch1, branch2));
    }

    private BinaryTree createBinaryTree() {
        final BinaryTree leaf1 = new BinaryTree("leaf1", null, null);
        final BinaryTree leaf2 = new BinaryTree("leaf2", null, null);
        final BinaryTree leaf3 = new BinaryTree("leaf3", null, null);
        final BinaryTree leaf4 = new BinaryTree("leaf4", null, null);
        final BinaryTree branch1 = new BinaryTree("branch1", leaf1, leaf2);
        final BinaryTree branch2 = new BinaryTree("branch2", leaf3, leaf4);
        return new BinaryTree("root", branch1, branch2);
    }
}

Note, that with Java 9 there will be the possibility to create private default methods, which would maybe enable us to hide the default <T extends FindableNode> Optional<T> findByName(final Set<T> nodes, final String name) method from the world, thus making the interface even more concise and clear. Since i haven't tried Java 9 yet i'm not sure if it would really work :D

Also we could get rid of the Type casts inside FindableNode using an inner class with a type parameter, but for the sake of simplicity, i deliberately left the typecasts in.

Happy Hacking!

Monday, July 6, 2015

Playing with the Java 8 Collectors API

I recently came across a problem that looked like it had to be a walk in the park using the Java 8 Collectors API. A short glance at the API doc with its myriad of angle brackets, one letter type parameters and predefined Collectors promised a type-safe solution just waiting to be discovered.

I did indeed find a solution to the problem quite quickly, but was not quite happy about the clumsy way it looked. This blog post is meant to share my findings trying to solve as simple a task as the one represented by the following Unit Test:

public class MyCollectorsTest {  
    private List<KeyValuePair> createKeyValuePairs() {
        return new ImmutableList.Builder<KeyValuePair>()
                .add(new KeyValuePair("java", "christoph"))
                .add(new KeyValuePair("java", "susanne"))
                .add(new KeyValuePair("scala", "susanne"))
                .add(new KeyValuePair("java", "martin"))
                .add(new KeyValuePair("java", "thomas"))
                .add(new KeyValuePair("java", "armin"))
                .add(new KeyValuePair("scala", "armin"))
                .build();
    }

    @Test
    public void testGroupByKeysAndJoinValues() {
        final Map<String, String> result = new MyCollectors().groupByKeysAndJoinValues(createKeyValuePairs());
        assertThat(result.size(), is(2));
        assertThat(result.get("java"), is("armin, christoph, martin, susanne, thomas"));
        assertThat(result.get("scala"), is("armin, susanne"));
    }
}

Version 1

    // Version 1: use groupingBy, get entrySet and collect it to a map, sorting the values in the values function
    public Map<String, String> groupByKeysAndJoinValuesVersion1(final List<KeyValuePair> tuples) {
        return tuples.stream()
                .collect(groupingBy(KeyValuePair::getTheKey))
                .entrySet()
                .stream()
                .collect(toMap(Map.Entry::getKey, this::sortAndJoin1));
    }

    private String sortAndJoin1(final Map.Entry<String, List<KeyValuePair>> e) {
        return e.getValue().stream()
                .map(KeyValuePair::getTheValue)
                .sorted()
                .collect(joining(", "));
    }

Well...it works but it feels kinda cumbersome to have to pick out the EntrySet's values inside the toMap() function only to get sorted values. Also i wasn't happy with the fact that sortAndJoin1() - as its name suggests - did more than one thing. Let's keep trying:

Version 2

    // Version 2: the same as version 1 but implemented with nested collectors
    public Map<String, String> groupByKeysAndJoinValuesVersion2(final List<KeyValuePair> tuples) {
        return tuples.stream()
                .collect(
                        groupingBy(
                                KeyValuePair::getTheKey,
                                mapping(
                                        KeyValuePair::getTheValue,
                                        collectingAndThen(toList(), this::sortAndJoin2)
                                )
                        )
                );
    }

    private String sortAndJoin2(final List<String> stringList) {
        return stringList.stream().sorted().collect(joining(", "));
    }

Oooook...this is basically the same code as above, but uses nested or downstream collectors. To be perfectly honest, i think that having the possibility of downstream collectors is great, but the syntax is rather awkward and it takes some weird formatting to make it readable at all. Furthermore, this solution did not solve the problem i had with version 1: a separate method for sorting and joining. Yes, i know i can nest lamdas until the cows come home, but eventually, some time in the far future someone else might need to read (and understand) my code and i don't want them to curse me when they have nightmares from my code ;)

Anyway, since i'm always sure, there's a better way to do anything i kept on trying and eventually came up with the following:

Version 3

    // Version 3: collect to TreeSet thus sorting the values.
    public Map<String, String> groupByKeysAndJoinValuesVersion3(final List<KeyValuePair> tuples) {
        return tuples.stream()
                .collect(
                        groupingBy(
                                KeyValuePair::getTheKey,
                                mapping(
                                        KeyValuePair::getTheValue,
                                        collectingAndThen(
                                                toCollection(TreeSet::new),
                                                (theSet) -> theSet.stream().collect(joining(", "))
                                        )
                                )
                        )
                );
    }

Using the mapping Collector and TreeSet i could remove the need to manually sort the collection before joining it. Anyway - a small victory given the fact that the code still looks complicated, even though it doesn't do much.

I thought i could avoid it, but it seemed the only way to get readable code was to write my own collector. So, with a little bit of help from IntelliJ completing the type arguments for me i set out in a last desparate try to achieve concise and readable code, removing all the details from the call site:

Version 4

    // Version 4: use custom collector to hide sorting and joining.
    public Map<String, String> groupByKeysAndJoinValuesVersion4(final List<KeyValuePair> tuples) {
        return tuples.stream()
                .collect(groupingBy(KeyValuePair::getTheKey, new KeyValuePairSetStringCollector()));
    }

    private static class KeyValuePairSetStringCollector implements Collector<KeyValuePair, Set<String>, String> {
        @Override
        public Supplier<Set<String>> supplier() {
            return TreeSet::new;
        }

        @Override
        public BiConsumer<Set<String>, KeyValuePair> accumulator() {
            return (strings, keyValuePair) -> strings.add(keyValuePair.getTheValue());
        }

        @Override
        public BinaryOperator<Set<String>> combiner() {
            return (keyValuePairs, keyValuePairs2) -> {
                keyValuePairs.addAll(keyValuePairs2);
                return keyValuePairs;
            };
        }

        @Override
        public Function<Set<String>, String> finisher() {
            return (set) -> set.stream().collect(joining(", "));
        }

        @Override
        public Set<Characteristics> characteristics() {
            return new HashSet<>();
        }
    }
The collector can be rewritten to use a bit less boilerplate like so:
    private static Collector<KeyValuePair, Set<String>, String> toKeyValuePairSet() {
        final Supplier<Set<String>> supplier = TreeSet::new;
        final BiConsumer<Set<String>, KeyValuePair> accumulator = 
            (strings, keyValuePair) -> strings.add(keyValuePair.getTheValue());
        final BinaryOperator<Set<String>> combiner = (keyValuePairs1, keyValuePairs2) -> {
            keyValuePairs1.addAll(keyValuePairs2);
            return keyValuePairs1;
        };
        final Function<Set<String>, String> finisher = (set) -> set.stream().collect(joining(", "));
        return Collector.of(supplier, accumulator, combiner, finisher);
    }

It may depend on the case at hand, but in this case maybe the custom Collector pollutes the call site's code least.

Conclusion

The collector's API is definitely powerful, i do have my honest doubts, however, that the more advanced features will gain a lot of popularity outside of library code. Maybe it was intended to be that way, i don't know. Java 8 gives the developer much better tools to transform and modify data than did its previous versions, but it is still a far cry from being as comfortable or intuitive as other languages.

In the end i decided i needed to know how this would look in scala.

The Final Version

In scala this is basically a three-liner:

keyValuePairs.groupBy(_.theKey).collect {  
  case (key: String, values: List[KeyValuePair]) =>
    (key, values.map(_.theValue).sorted.mkString(", "))
}

Happy Hacking :)