Search code examples
javagenericsjava-8functional-programmingbounded-wildcard

capture#1-of ? super C interpreted as C in generic interface


Given the following classes:

class A { public A a() {return new A();}; };
class B extends A { public B b() {return new B();} };
class C extends B { public C c() {return new C();} };
class D extends C { public D d() {return new D();} };

I want to write some UnaryOperators that can accept instances of A, B and C, but not D.

So my choice for declaring the reference type is UnaryOperator<? super C>. Since I'm using super, it means it can also accept an instance of Object.

UnaryOperator<? super C> op1 = arg -> arg.a(); // does not compile, arg could be Object
UnaryOperator<? super C> op2 = arg -> arg.b(); // does not compile, arg could be Object
UnaryOperator<? super C> op3 = arg -> arg.c(); // does compile
UnaryOperator<? super C> op4 = arg -> arg.d(); // this is not expected to compile

Why does op1 makes the code fail to compile with a message

Type mismatch: cannot convert from A to C

and op2 makes the code fail to compile with a message

Type mismatch: cannot convert from B to C,

but op3 compiles fine, and let me call a method available only in C?


Solution

  • Why does op1 makes the code fail to compile...?

    When a generic functional interface is parameterized by wildcards, there are different instantiations that could satisfy the wildcard and produce different function types. For example, each of 1:

    UnaryOperator<C> (function type C -> C);
    UnaryOperator<B> (function type B -> B);
    UnaryOperator<A> (function type A -> A);
    UnaryOperator<Object> (function type Object -> Object);
    

    is a UnaryOperator<? super C>.

    Sometimes, it's possible to known from the context, such as the parameter types of a lambda, which function type is intended. Other times, it is necessary to pick one:

    UnaryOperator<? super C> op1 = (A arg) -> arg.a();
                                    ^
    
    UnaryOperator<? super C> op2 = (B arg) -> arg.b();
                                    ^
    ...
    

    If you don't pick one, the bounds are used 2:

    UnaryOperator<? super C> op = arg -> arg.c(); // valid
    

    Where C is a bound, so the lambda expression is a UnaryOperator<C> in this case.


    1 - JLS 9.9. Function Types - the last paragraph.
    2 - JLS 15.27.3. Type of a Lambda Expression - if T is a wildcard-parameterized functional interface type and the lambda expression is implicitly typed, then the ground target type is the non-wildcard parameterization T.