Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 generic collections with optionals

I have a relatively simple looking problem that I am trying to solve. There doesn't seem to be an intuitive way to do this or, I am missing something here.

Consider this method to find the main image and if none exists, return first image-

public Image findMainImage(Collection<? extends Image> images) {
    if (images == null || images.isEmpty()) return null
    return images.stream()
                 .filter(Image::isMain)
                 .findFirst()
                 .orElse(images.iterator().next())
}

I get an error - orElse(capture<? extends Image>) in Optional cannot be applied

Any direction on this would be great.

like image 279
sanz Avatar asked Apr 24 '15 22:04

sanz


2 Answers

One way to fix it is to use a type parameter:

public <I extends Image> I findMainImage(Collection<I> images) {
    if (images == null || images.isEmpty()) return null;
    return images.stream()
                 .filter(Image::isMain)
                 .findFirst()
                 .orElse(images.iterator().next());
}

Because then (to the compiler) the Optional definitely has the same type argument as images.

And we could use that as a capturing helper if we wanted:

public Image findMainImage(Collection<? extends Image> images) {
    return findMainImageHelper( images );
}

private <I extends Image> I findMainImageHelper(Collection<I> images) {
    // ...
}

Personally, I would just use the generic version because then you can do e.g.:

List<ImageSub> list = ...;
ImageSub main = findMainImage( list );

Basically...the reasoning for why it doesn't compile originally is to keep you from doing something like this:

public Image findMainImage(
    Collection<? extends Image> images1,
    Collection<? extends Image> images2
) {
    return images1.stream()
                  .filter(Image::isMain)
                  .findFirst()
                  .orElse(images2.iterator().next());
}

And in the original example the compiler doesn't need to determine the fact that both the Stream and the Iterator come from the same object. Two separate expressions that reference the same object get captured to two separate types.

like image 102
Radiodef Avatar answered Sep 30 '22 03:09

Radiodef


Suppose you have a List<? extends Number>. You could not add any number to this list, because it could be a List<Integer> and the number you are attempting to add could be a Float.

For the same reason, the method orElse(T t) of Optional<T> requires a T. Since the Optional in question is an Optional<? extends Image>, the compiler can't be sure that images.iterator().next() is of the right type.

I got this to compile by putting in .map(t -> (Image) t):

return images.stream()
             .filter(Image::isMain)
             .findFirst()
             .map(t -> (Image) t)
             .orElse(images.iterator().next());

In fact, for some reason I can't understand, it works even without the cast. Just using

.map(t -> t)

seems to make this work.

like image 37
Paul Boddington Avatar answered Sep 30 '22 04:09

Paul Boddington