Invalid method reference with generics

Nick Williams nicholas+openjdk at nicholaswilliams.net
Wed Jun 26 10:43:58 PDT 2013


On Jun 26, 2013, at 12:27 PM, Maurizio Cimadamore wrote:

> On 26/06/13 18:13, Nick Williams wrote:
>> That compiles and runs (I just tested it), but the two uses of register are completely opposite. Register with the anonymous inner class works as I expect: I can pass in an ApplicationListener whose type_extends_  ApplicationEvent. Register with the method reference is opposite: I can pass in an ApplicationListener whose type_is extended by_  ApplicationEvent. I am very convinced that that's wrong. In fact, my IDE highlights the anonymous inner class as a warning that says "Anonymous ApplicationListener<Event1>" can be replaced with lambda," but when I let it replace it I get incompatible parameter types upon compilation.
> I think the fact that the IDE suggests that a lambda might be use is a bug in the lambda detector; a lambda cannot be used as, a lambda parameter type must be identical to that of the descriptor itr will be passed to. Subtyping is only allowed for method references, but the other way around, not the one you are asking for.
> 
> Example:
> 
> interface I {
>   void m(Number n)
> }
> 
> void g1(Object o)
> void g2(Number n)
> void g3(Integer i)
> 
> void m(I i);
> 
> m(this::g1); //ok, Number subtype of Object
> m(this::g2); //ok, Number subtype of Number
> m(this::g3); //error, Number not a subtype of Integer
> 
> Note that this semantics is quite standard, as parameter types in function types are treated in a contravariant fashion (i.e. the function type accepting a more constrained domain is more general - this comes from Liskov - a subtype of a function type cannot start arbitrarily rejecting input values that were accepted by a super type).

Indeed, this behavior makes sense _for methods whose argument types aren't indicated by type variables_. In my case, "interface I" is actually "interface ApplicationListener<E extends ApplicationEvent>" and "m(Number n)" is actually "m(E e)." The argument type is, by definition, _intended_ to be more restrictive in implementing classes. My thought is "if it could be done with an anonymous inner class, it should be able to be done with a lambda expression." Weren't lambda expressions largely meant to replace any and all legal anonymous inner classes implementing one-method interfaces?

To continue with your analogy above, it makes sense because I COULD NOT do this:

m(new I() {
    public void m(Integer i) { }
});

That would naturally result in a compile error, as it should, because I#m() takes a Number and Integer is more restrictive. However, I CAN do this (it compiles and runs just fine):

register(new ApplicationListener<Event1>() {
    public void onApplicationEvent(Event1 event) { }
});

So, by extension, I SHOULD be able to do this, but can't:

void onApplicationEvent(Event1 event) { }
register(this::onApplicationEvent);

We have inconsistent behavior here. In your example, both the method reference and the anonymous inner class are illegal. In my use case, the compiler allows the anonymous inner class but prohibits the method reference. That inconsistency is a real problem. There is _no_ semantic difference between what I'm doing with the anonymous inner class and what I'm doing with the method reference. Zero. The method signatures are identical (I event named them the same here to drive home the point), but one works and the other doesn't.

Nick



More information about the lambda-dev mailing list