Search code examples
javapolymorphismfunctional-interfacedefault-method

Interface overriding extending functional interface method as default and having an abstract method works abruptly


@FunctionalInterface
interface StringConsumer extends Consumer<String> {
    @Override
    public default void accept(String s) {
        System.out.println(s.toUpperCase());
    }
    void print(String s);
}

public class Test {
    public static void main(String[] args) {
        StringConsumer stringConsumerImpl= s -> System.out.println(s.toLowerCase());
        List<String> list = Arrays.asList("Dr", "Mr", "Miss", "Mrs");
        list.forEach(stringConsumerImpl);
    }
}

Output: DR MR MISS MRS

So what is going on in this code is really weird for me. I am actually providing implementation of print method but when I run the code it actually uses implementation of default method.

I was expecting print implementation to work. edit: when print method is deleted from StringConsumer it causes compilation error on main method


Solution

  • Iterable.forEach is implemented like this:

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    

    It calls the accept method of the Consumer you passed in, so that is why accept is called and not print. It is as simple as that.

    You might have thought that forEach will call whatever lambda expression you passed in. This is not true. Lambda expressions are just a fancy way to create an implementation of an interface that has a single abstract method. The body of the lambda expression is the implementation of that single abstract method (print). It just saves you the trouble of writing a class like this and creating an instance of it:

    class Foo implements StringConsumer {
        @Override
        public void print(String s) {
            System.out.println(s.toLowerCase());
        }
    }
    

    That interface is free to have other default methods and other methods can choose to call those default methods, instead of the abstract method the lambda implements. It's just like every other variable of an interface type.