6

I have the following piece of code:

eventBus.subscribe(new EventBusListener<NavigationEvent>() {
    @Override
    public void onEvent(Event<NavigationEvent> event) {
        event.getPayload();
    }
});

eventBus.subscribe(new EventBusListener<NotificationEvent>() {
    @Override
    public void onEvent(Event<NotificationEvent> event) {
        event.getPayload();
    }
});

IntelliJ tells me that I could replace those two anonymous classes by lambda expressions, like:

eventBus.subscribe((EventBusListener<NavigationEvent>) Event::getPayload);
eventBus.subscribe((EventBusListener<NotificationEvent>) Event::getPayload);

Compilation works well but at runtime the application crashes with the following error: java.lang.IllegalArgumentException: Could not resolve payload type caused by the getPayload() of Event<T> class.

What am i missing with lamdbas and generics?

louis amoros
  • 2,418
  • 3
  • 19
  • 40
  • 1
    use `Event::getPayload` to explicit the type – njzk2 May 16 '17 at 15:19
  • 1
    as a side note, those are not lambdas, those are function references. It wouldn't work with lambdas, as lambdas can't be used for functional interfaces with generic types – njzk2 May 16 '17 at 15:20
  • 1
    Possible duplicate of [Lambda Expression and generic method](http://stackoverflow.com/questions/22588518/lambda-expression-and-generic-method) – njzk2 May 16 '17 at 15:20
  • Thnx for the note. Still get the same error with `Event::getPayload` – louis amoros May 16 '17 at 15:24
  • What are you doing with the object returned by the `getPayload()` method? – fps May 16 '17 at 15:25
  • 3
    Can you share the whole stacktrace? – ZhekaKozlov May 16 '17 at 15:25
  • 2
    @njzk2: `getPayload` is not a generic method, hence, using `Event::getPayload` makes no sense, the type argument will be ignored. And lambda expressions will work, well to the same degree as the method references. – Holger May 16 '17 at 16:06
  • 1
    @Holger now that you mention it, yeah, I read all of that way too fast. it doesn't make sense indeed. – njzk2 May 16 '17 at 16:09

1 Answers1

7

There is nothing wrong with your method references, but the IllegalArgumentException hasn’t been thrown by that subsystem either.

It seems the problem is connected to what eventBus.subscribe does internally. Due to type erasure, there is only one subscribe(EventBusListener) method, not knowing whether you passed in an EventBusListener<NavigationEvent> or EventBusListener<NotificationEvent>.

As the message “Could not resolve payload type” indicates, it tries to find out the actual payload type anyway, which implies using Reflection. This only works for reifiable, i.e. non-generic, types. Your anonymous inner classes are reifiable types, not being generic themselves but having a generic super class/interface which can be inspected via getGenericSuperclass() resp. getGenericInterfaces().

The problem with using method references or lambda expressions in that context, is, that if the interface is generic, the generated runtime class will not be reifiable, i.e. you can’t find out the actual type parameter via Reflection. It’s like if you hay written

eventBus.subscribe(Event::getPayload);

Unless there is an overloaded method allowing you to specify the payload type explicitly, this will not work with lambda expressions or method references.

Depending, how the framework does resolving the type internally, using a reifiable interface might work, e.g.

interface NavigationEventListener extends EventBusListener<NavigationEvent> {}
…

eventBus.subscribe((NavigationEventListener)Event::getPayload);

of course, this would only pay off if you are going to instantiate more than one listener of such a type, otherwise, you could stay with the anonymous inner class (unless their capturing of the this instance is your issue).

Holger
  • 285,553
  • 42
  • 434
  • 765