Java default interface methods concrete use cases
Asked Answered
L

5

13

Java 9 is near to come and more features will be added to Java interfaces, like private methods. default methods in interfaces were added in Java 8, essentially to support the use of lambdas inside collections without breaking retro-compatibility with previous versions of the language.

In Scala, methods inside traits are quite useful. However, Scala has a different approach treating traits than Java with default methods. Think to multiple inheritance resolution or the use of traits as mixins.

Apart the above use, which are the real scenarios in which using a default methods is worth? During this years is it arisen some pattern that uses them? Which problems can I solve using this kind of methods?

Lederman answered 27/4, 2017 at 15:1 Comment(15)
I have known one and it is powerful in functional interface, can I point it out?Sandoval
@Sandoval why not?Lederman
I think there are many design patterns can be used in an interface further.e.g: using static factory method to archive the Factory pattern, using default methods to implements Template Method Pattern, and I introduced a Decorator pattern in my answer, and .etc.Sandoval
Besides applying patterns as @Sandoval suggests, you can use them to program in a more functional way, i.e. you could have a Function3 functional interface that accepts 3 parameters and you could provide i.e. binding of parameters (also known as partial application) and currying via default methodsDreda
@holi-java, static factory method are you sure? Can you give me an example? It smells of violation of Open-Close Principle :PLederman
@Lederman See i.e. Comparator for static factory method. There's no violation of Open-close principleDreda
there is a example in junit5 using default methods implements Template Method Pattern, so I don't introduce it here.Sandoval
@FedericoPeraltaSchaffner this question like the question of design patterns, so you can answer your mind.Sandoval
" static factory method are you sure?" - in my answer already having a static factory method will(Answer).Sandoval
@FedericoPeraltaSchaffner concrete use cases of default methods are not so easy to retrieve. Then, my intention was that of create a sort of vademecum.Lederman
@FedericoPeraltaSchaffner sir, "currying via default methods" is another design pattern - Adapter Pattern.Sandoval
@Sandoval Yes, it actually is, you are right. The thing with functional programming and OO design patterns is that sometimes functional programming makes use of many OO design patterns implicitly. See i.e. this question.Dreda
@FedericoPeraltaSchaffner thanks for you feedback. so this question is no answer, because it is too big to answer.Sandoval
@Lederman So you'd like this question and its answers to become something like a blog post? If yes, I will add some use cases.Dreda
@FedericoPeraltaSchaffner why not? sharing your mind to everybody please including me.Sandoval
P
18

Brian Goetz and I covered some of this at our JavaOne 2015 talk, API Design with Java 8 Lambda and Streams. Despite the title, there is some material at the end about default methods.

Slides: https://stuartmarks.files.wordpress.com/2015/10/con6851-api-design-v2.pdf

Video: https://youtu.be/o10ETyiNIsM?t=24m

I'll summarize here what we said about default methods.

Interface Evolution

The primary use case of default methods is interface evolution. Mainly, this is the ability to add methods to interfaces without breaking backward compatibility. As noted in the question, this was most prominently employed to add methods allowing conversion of Collections to Streams and to add lambda-based APIs to Collections.

There are several other use cases, though.

Optional Methods

Sometimes interface methods are logically "optional". Consider mutator methods on immutable collections, for example. Of course, an implementation is required, but usually what it will do in such cases is to throw an exception. This can easily be done in a default method. Implementations can inherit the exception-throwing method if they don't want to provide it, or they can override it if they want to provide an implementation. Example: Iterator.remove.

Convenience Methods

Sometimes a method is provided for the convenience of callers, and there is an obvious and optimal implementation. This implementation can be provided by a default method. It's legal for an implementation to override the default, but there's generally no reason, so implementations will usually inherit it. Examples: Comparator.reversed, Spliterator.getExactSizeIfKnown, Spliterator.hasCharacteristics. Note that Spliterator was introduced in Java 8, including the default methods, so this clearly wasn't a case of interface evolution.

Simple Implementation, Intended to be Overridden

A default method can provide a simple, general implementation that works for all implementations, but that is probably suboptimal. This assists implementations during initial bring-up, because they can inherit the default and be assured of correct operation. However, in the long term, implementations will probably want to override the default and provide an improved, customized implementation.

Example: List.sort. The default implementation copies the list elements to a temporary array, sorts the array, and copies the elements back to the list. This is a correct implementation, and sometimes it can't be improved upon (e.g. for LinkedList). However, ArrayList overrides sort and sorts its internal array in-place. This avoids the copying overhead.

Now, obviously sort was retrofitted onto List and ArrayList in Java 8, so the evolution didn't happen this way. But you could easily imagine bringing up a new List implementation. You'd probably initially inherit the sort default implementation while you're getting the basics implemented properly. Later on, you might consider implementing a customized sort algorithm that's tuned to your new implementation's internal data organization.

Philender answered 28/4, 2017 at 19:45 Comment(1)
does default interface violate interface isolation priciple?Komarek
D
7

First that comes to mind is the use of default methods to support some functional programming techniques:

@FunctionalInterface
public interface Function3<A, B, C, D> {

    D apply(A a, B b, C c);

    default Function<A, Function<B, Function<C, D>>> curry() {
        return a -> b -> c -> this.apply(a, b, c);
    }

    default Function<B, Function<C, D>> bindFirst(A a) {
        return b -> c -> this.apply(a, b, c);
    }
}

Sample usage:

Function3<Long, Long, Long, Long> sum = (a, b, c) -> a + b + c;
long result = sum.apply(1L, 2L, 3L); // 6

Function<Long, Function<Long, Function<Long, Long>>> curriedSum = sum.curry();
result = curriedSum.apply(1L).apply(2L).apply(3L); // 6

Function<Long, Function<Long, Long>> incr = sum.bindFirst(1L);
result = incr.apply(7L).apply(3L); // 11
result = incr.apply(6L).apply(7L); // 14

You can have similar binding methods for the other parameters, implemented with default methods, such as bindSecond and bindThird.

You can use default methods to decorate the parent interface (as @holi-java explains in his answer), also there a lot of examples of the adapter pattern (currying and binding are actually adapters).


Besides functional programming, you can use default methods to support kind of, limited multiple inheritance:

public interface Animal {

    String getHabitat();
}

public interface AquaticAnimal extends Animal {

    @Override
    default String getHabitat() {
        return "water";
    }
}

public interface LandAnimal extends Animal {

    @Override
    default String getHabitat() {
        return "ground";
    }
}

public class Frog implements AquaticAnimal, LandAnimal {

    private int ageInDays;

    public Frog(int ageInDays) {
        this.ageInDays = ageInDays;
    }

    public void liveOneDay() {
        this.ageInDays++;
    }

    @Override
    public String getHabitat() {
        if (this.ageInDays < 30) { // is it a tadpole?
            return AquaticAnimal.super.getHabitat();
        } // else
        return LandAnimal.super.getHabitat();
    }
}

Sample:

Frog frog = new Frog(29);

String habitatWhenYoung = frog.getHabitat(); // water

frog.liveOneDay();
String habitatWhenOld = frog.getHabitat(); // ground

Maybe not the best example, but you get the idea...


Another usage would be traits:

public interface WithLog {

    default Logger logger() {
        return LoggerFactory.getLogger(this.getClass());
    }
}

public interface WithMetrics {

    default MetricsService metrics() {
        return MetricsServiceFactory.getMetricsService(
            Configuration.getMetricsIP(
                Environment.getActiveEnv())); // DEV or PROD
    }
}

Now, whenever you have a class that needs to log something and report some metrics, you could use:

public class YourClass implements WithLog, WithMetrics {

    public void someLongMethod() {

        this.logger().info("Starting long method execution...");

        long start = System.nanoTime();

        // do some very long action

        long end = System.nanoTime();

        this.logger().info("Finished long method execution");

        this.metrics().reportExecutionTime("Long method: ", end - start);
    }
}

Again, this is not the best possible implementation, but just sample code to see how traits can be used via default methods.

Dreda answered 27/4, 2017 at 16:49 Comment(2)
I'm sorry I left for a while. I like the curry section. in the second section I like to use a strategy rather than process approach.Sandoval
@Sandoval Yes, maybe not the best example, I would also use a strategy instead of an if statement...Dreda
P
5

Well I have a real world scenario in which I've used them. Here is the context: I get a result from google maps api (by providing latitude and longitude) in the form of an Array of results, that looks like this:

GeocodingResult[] result

That result contains some information that I need, like zip-code or locality or country. Different services need different parts of that response. The parsing of that array is the same - you just need to search for different parts.

So I've defined that in a default method inside the interface:

default Optional<String> parseResult(
        GeocodingResult[] geocodingResults, 
        AddressComponentType componentType,// enum
        AddressType addressType) { // enum

     ... Some parsing functionality that returns
      city, address or zip-code, etc
}

Now in the implementation of the interface I just use this method.

 class Example implements Interface {

      @Override
      public Optional<String> findZipCode(Double latitude, Double longitude) {
         LatLng latLng = new LatLng(latitude, longitude);
         return parseResult(latLng, 
             AddressComponentType.POSTAL_CODE, 
             AddressType.POSTAL_CODE);
      }


    .. other methods that use the same technique

This used to be done via abstract classes. I could have used a private method, but this interface is used by many other services.

Purgatorial answered 27/4, 2017 at 20:34 Comment(10)
So, can we say that if we do not need a state, an interface with default methods can do the job instead of an abstract class?Lederman
@Lederman exactly what I had in mind when writing it like this. Besides I keep the code close to where it's needed, if that makes sense. But that is something not to abuse, of course.Purgatorial
This is a big change in how we design Java code, isn't it? I am asking if someone else out there made the same reasoning of ours.Lederman
@Lederman and Eugene, this seems like a static method to me. It parses the first argument based on the other arguments. Could be perfectly moved to a utility class. I don't see the benefit of having this functionality in a default method.Dreda
@Purgatorial first vote up, may be the default method violate SRP principle.Sandoval
@FedericoPeraltaSchaffner static method does not support polymorphism, when you want to have a couple of parseResult variations.Sandoval
@FedericoPeraltaSchaffner utility classes with static methods should be avoided. They smells of procedural programming and difficult to manage as dependencies (think about unit tests). I see the Eugene example as an evolution of the so called companion types.Lederman
@Sandoval Agreed, given that those variations depend on the typeDreda
@Lederman I'm not saying that we should use static methods in utility classes all the type. My point was that this example of default method reminded me of that. However, I think you convinced me. I see this approach more like traits.Dreda
@FedericoPeraltaSchaffner I found this link: Everything You Need To Know About Default Methods. Hope it helps.Lederman
S
4

Decorate the Function Interface Chaining with default methods

I want to chain @FunctionalInterface sometimes, and we have already seen in Function having default methods for chaining the function, e.g:compose, andThen to make the code more elegant. and the most important is we can reuse the partial function later, for example:

Predicate<?> isManager = null;
Predicate<?> isMarried = null;

marriedManager = employeeStream().filter(isMarried.and(isManager));
unmarriedManager = employeeStream().filter(isMarried.negate().and(isManager));

However, sometimes we can't chain the @FunctionalInterface since it have not been provided any chain methods. but I can write another @FunctionalInterface extends the original ones and adding some default methods for chaining purpose. for example:

when(myMock.myFunction(anyString()))
       .then(will(returnsFirstArg()).as(String.class).to(MyObject::new));

this is the answer of mine yesterday: Mockito returnsFirstArg() to use. due to Answer has no chain methods, so I introduce another Answer type AnswerPipeline to provide chain methods.

AnswerPipeline class

interface AnswerPipeline<T> extends Answer<T> {

    static <R> AnswerPipeline<R> will(Answer<R> answer) {
        return answer::answer;
    }

    default <R> AnswerPipeline<R> as(Class<R> type) {
        return to(type::cast);
    }

    default <R> AnswerPipeline<R> to(Function<T, R> mapper) {
        return it -> mapper.apply(answer(it));
    }
}
Sandoval answered 27/4, 2017 at 15:19 Comment(0)
S
4

Removing the Default-Adapter of Listener by using Default Methods

Sometimes, we need to introduce a default Adapter class for the java.util.EventListener which has multiple events need to trigger, but we only interested in some of events. for example: swing create each *Adapter class for each *Listener.

I recently found this is very useful when we declare listeners with default methods we can remove the middle adapter class. for example:

interface WindowListener extends EventListener {

    default void windowOpened(WindowEvent e) {/**/}

    default void windowClosing(WindowEvent e) {/**/}

    default void windowClosed(WindowEvent e) {/**/}
}
Sandoval answered 15/5, 2017 at 17:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.