Search code examples
javagenericsjava-stream

Why Java doesn't a Stream that returns list of generics work?


Why does this work:

private List<Optional<Event>> createEventsStartingWith(List<Long> forwardIds, int startAt, final AnnouncementUpdatedEventPayload beforeChanges) {
    List<Optional<Event>> announcementDeletedAppEvents = new ArrayList<>();
    for (Long forwardId : forwardIds.subList(startAt, forwardIds.size())) {
        announcementDeletedAppEvents.add(Optional.of(new AnnouncementDeletedAppEvent(forwardId, eventMapper.mapToNotificationPayload(beforeChanges))));
    }
    return announcementDeletedAppEvents;
}

but this doesn't work:

compilation error:
Required type: List<Optional<Event>>

Provided: List<Optional<AnnouncementDeletedAppEvent>>
private List<Optional<Event>> createEventsStartingWith(List<Long> forwardIds, int startAt, final AnnouncementUpdatedEventPayload beforeChanges) {
    return IntStream.range(forwardIds.indexOf(startAt), forwardIds.size())
                .mapToObj(forwardId -> Optional.of(new AnnouncementDeletedAppEvent(forwardId, eventMapper.mapToNotificationPayload(beforeChanges))))
                .toList();
}

where:

public class AnnouncementDeletedAppEvent extends Event{
    long Id;
    AnnouncementPayload payload;
}

Solution

  • This is because Generics are invariant in Java.

    Let's look at the expression you are returning:

    IntStream.range(forwardIds.indexOf(startAt), forwardIds.size())
            .mapToObj(forwardId -> Optional.of(new AnnouncementDeletedAppEvent(forwardId, eventMapper.mapToNotificationPayload(beforeChanges))))
            .toList();
    

    Specifically, the mapToObj method will give you a Stream<AnnouncementDeletedAppEvent> and not a Stream<Event> because javac sees that it will always be an Optional<AnnouncementDeletedAppEvent> (instead of an Optional<Event>) and it tries to find the most general type and it does not propagate type information backwards through methods.

    Then, toList() converts the result to a List<Optional<AnnouncementDeletedAppEvent>>. Since generics are invariant, a List<Optional<AnnouncementDeletedAppEvent>> is not a List<Optional<Event>>.

    You can fix this issue by telling Java you just want events using an additional type paramer (you can explicitely the type used in Optional.of) by adding <Event> before the of (Optional.<Event>of(...)):

    return IntStream.range(forwardIds.indexOf(startAt), forwardIds.size())
            .mapToObj(forwardId -> Optional.<Event>of(new AnnouncementDeletedAppEvent(forwardId, eventMapper.mapToNotificationPayload(beforeChanges))))
            .toList();
    

    Alternatively, you can cast the AnnouncementDeletedAppEvent to an Event before calling Optional.of:

    return IntStream.range(forwardIds.indexOf(startAt), forwardIds.size())
            .mapToObj(forwardId -> Optional.of((Event) new AnnouncementDeletedAppEvent(forwardId, eventMapper.mapToNotificationPayload(beforeChanges))))
            .toList();