Java 9: Cleaning up your default methods using private interface methods

Java 8 brought huge changes — one of them being the default interface methods. These methods changed interfaces as we knew them — we could now define default implementations in the interface itself.

If you use these default methods heavily in your applications, you'll quickly figure out one thing — it doesn't really scale. Why? Because you can't extract duplicated code into common methods — until now.

In Java 9 this is solved by introducing private interface methods. These methods allows you to implement private methods in the interface. This way we can structure the code implemented in the interface in a clean way without duplication.

Refactoring default methods to take advantage of private interface methods

To see how this is done, let's look at some code.

Say you got an interface — Archive.

public interface Archive {

  List<Article> getArticles();

  default List<Article> filterByTitle(String title) {
    return getArticles().stream()
      .filter(article -> article.getTitle().equals(title))
      .collect(Collectors.toList());
  }

  default List<Article> filterByTag(String tag) {
      return getArticles().stream()
        .filter(article -> article.getTags().contains(tag))
        .collect(Collectors.toList());
  }
}

As you can see, Archive contains one abstract method — getArticles — and two default methods — filterByTitle and filterByTag.

Now, if you take a closer look at the two default methods, they contains almost the same code. The only thing that distinguish them is the predicate used in the filter method.

This type of duplication is not pretty and should be unnecessary. There is no reason why "clean code" shouldn't be applied to the default methods as well. Luckily, the new private interface methods can help us out.

Let's refactor our previous implementation to use private interface methods.

public interface NewArchive {

  List<Article> getArticles();

  default List<Article> filterByTitle(String title) {
    return filterBy(article -> article.getTitle().equals(title));
  }

  default List<Article> filterByTag(String tag) {
    return filterBy(article -> article.getTags().contains(tag));
  }

  private List<Article> filterBy(Predicate<Article> toFilterBy) {
    return getArticles().stream()
      .filter(toFilterBy)
      .collect(Collectors.toList());
  }
}

There we go!

By extracting everything except the predicates into filterBy we've removed the duplication as well as making our interface read way better.

Enjoyed the post?

If you don't want to miss future posts, make sure to subscribe