3

I recently came across this when trying to provide common functionality to a sub-class of a functional interface. Apparently you cannot do it the way I thought (atleast for Java 8):

@FunctionalInterface 
public interface Base<T extends Base<T>> {
    void doit();
        
    default T someDefault() {
        return () -> {}; //incompatible types: T is not a functional interface
    }
}

Is there a way to do this? Or was it intentionally disallowed? Maybe an abuse of default methods?

Maybe a use-case example would be a good idea:

@FunctionalInterface 
public interface Processor<I, T extends Processor<I, T>> {
    T process(I input);
    default T requireNonNull(I i) {
        return (I input) -> {
            Objects.requireNonNull(i);
            process(input);
        };
    }
}
    
@FunctionalInterface 
public interface Parser<T> extends Processor<String, T> {
    @Override
    default T process(String s) { 
        return parse(s);
    }
    T parse(String s);
}
voidnull
  • 53
  • 4
  • 2
    `T` extends `Base`. The extending `T` is not guaranteed to be a functional interface, it could have added a method, making it not a SAM-type anymore (e.g. `interface TChild extends Base { void secondMethod(); }` – knittl Apr 15 '23 at 19:55
  • 1
    It's not just that it could have added a method, it could be a class and not an interface. Nothing like this can possibly work. – Louis Wasserman Apr 16 '23 at 08:49
  • 2
    It’s a lucky coincidence that in your example, the `process` method has the same parameter types and return type as the `parse` method. Since nothing in the language enforces this, you have demonstrated yourself that even if the sub-interface is a functional interface it may have a different method to implement and there’s no guaranty that the lambda expression in the default method is compatible to the other method. – Holger Apr 18 '23 at 06:50

1 Answers1

0

As said in a comment, it is not guaranteed the interface extending the Base interface can be expressed the same way. The only possible is to change the return type to Base<T> to make returning the lambda legal, which is guaranteed by the Base interface:

default Base<T> someDefault() {
    return () -> {};
}

Though it is probably what you don't want. Remember that the ultimate condition you need to satisfy that an interface can be expressed through a lambda expression is that the interface has exactly one abstract (non-default) method, which can be achieved also through extending a different interface.

Here is an example using the Base interface that demonstrates why it is not possible to achieve the way you do:

public interface StringBase extends Base<StringBase> {

    void bonk();
}

Now StringBase cannot be expressed through a lambda expression as long as it provides two abstract methods.

Theoretically, only and only if all interfaces extending the Base interface can be compiled with @FunctionalInterface would assure that each of them would satisfy such a condition. However, there is no mechanism in Java to enforce this for the sake of successful compilation.

One of the workarounds is to implement the someDefault method and provide it as default in the extending interface. This assures the instantiated interface has truly a single abstract method and there is no dispute as in the Base interface:

public interface Base<T extends Base<T>> {

    void doIt();

    T someDefault();
}

@FunctionalInterface
public interface StringBase extends Base<StringBase> {

    @Override
    default StringBase someDefault() {
        return () -> {};
    }
}
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183