Search code examples
javalambdajava-8default-methodfunctional-interface

Functional Interface Inheritance Quirk


I have a custom interface I've been using for some time that looks something like this:

public interface Function<T, R> {
    R call(T input);
}

I'd like to retrofit this interface with both Java's Function as well as Guava's Function, while keeping it a FunctionalInterface. I thought I had the perfect arrangement:

@FunctionalInterface
public interface Function<T, R> extends
        java.util.function.Function<T, R>,
        com.google.common.base.Function<T, R> {

    R call(T input);

    @Override
    default R apply(T input) {
        return call(input);
    }
}

Both superinterfaces declare the same apply() method, which has been implemented in my interface, leaving only the abstract call() method. Strangely, it won't compile, telling me

Invalid '@FunctionalInterface' annotation; Function<T,R> is not a functional interface

Stranger still, the following variations compile just fine:

@FunctionalInterface
public interface Function<T, R> extends
        java.util.function.Function<T, R> {

    R call(T input);

    @Override
    default R apply(T input) {
        return call(input);
    }
}

@FunctionalInterface
public interface Function<T, R> extends
        com.google.common.base.Function<T, R> {

    R call(T input);

    @Override
    default R apply(T input) {
        return call(input);
    }
}

public interface Function<T, R> extends
        java.util.function.Function<T, R>,
        com.google.common.base.Function<T, R> {

    R call(T input);

    @Override
    default R apply(T input) {
        return call(input);
    }
}

@FunctionalInterface
public interface Function<T, R> extends
        java.util.function.Function<T, R>,
        com.google.common.base.Function<T, R> {

    @Override
    R apply(T input);
}

Is there a reason the first version won't compile?


Solution

  • As stated in the comments, it compiles fine with the oracle compiler. It is an eclipse bug.

    Awaiting for a bug fix, personally i will remove the annotation @FunctionalInterface (your 3rd variation):

    public interface Function<T, R>
                                    extends
                                        java.util.function.Function<T, R>,
                                        com.google.common.base.Function<T, R> {
    
        R call(T input);
    
        @Override
        default R apply(T input) {
            return call(input);
        }
    }
    

    The major inconvenient of this solution is that the eclipse compiler bug prevent from using the Function as a lambda target type.


    If you really want to keep @FunctionalInterface on your Function, a (ugly) workaround might be to introduce an intermediate interface:

    public interface AdapterFunction<T, R>
                                          extends
                                              java.util.function.Function<T, R>,
                                              com.google.common.base.Function<T, R> {
        @Override
        default R apply(T input) {
            return null;
        }
    }
    

    and let your Function extends this AdapterFunction:

    @FunctionalInterface
    public interface Function<T, R>
                                    extends
                                        AdapterFunction<T, R> {
    
        R call(T input);
    
        @Override
        default R apply(T input) {
            return call(input);
        }
    }
    

    In this case, the Function is a valid target type for eclipse too:

    Function<String, Object> function = st -> st.toString();