Search code examples
javagenericsinheritancefunctional-interface

Why can't I cast the result of the super call directly?


So I have a interface that extends BiConsumer.

@FunctionalInterface
public interface MyActionWithParameters<E, P> extends BiConsumer<Selection<E>, P> {
    @Override
    default MyActionWithParameters<E, P> andThen(BiConsumer<? super Selection<E>, ? super P> after) {
        var newAction = BiConsumer.super.andThen(after);
        return newAction::accept;
    }
}

This works.

But if I try to return (MyActionWithParameters<>) BiConsumer.super.andThen(after);, I get a class cast exception.

Why is that? Why can I cast newAction::accept (i.e. a new BiConsumer) but not the BiConsumer itself? I have a feeling the answer should be pretty obvious, but I just don't see it.

EDIT: full error

Caused by: java.lang.ClassCastException: class java.util.function.BiConsumer$$Lambda$2165/0x0000000840d10840 cannot be cast to class blah.blah.blah.MyFoo$MyActionWithParameters (java.util.function.BiConsumer$$Lambda$2165/0x0000000840d10840 is in module java.base of loader 'bootstrap'; blah.blah.blah.MyFoo$MyActionWithParameters is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @739fc679)

Solution

  • The reason why casting does not work is because whatever BiConsumer.andThen returns, it does not implement MyActionWithParameters. How on earth is BiConsumer, a JDK interface, supposed to know about your own custom interface? :-)

    But using a method reference works, because by using a method reference, the type of object that super.andThen returns, aka newAction, no longer matters. What matters is the method - accept. Does accept have a compatible signature to satisfy the functional interface of MyActionWithParameters? Yes it does, so it works.