2015-04-24 15 views
5

Ich habe ein relativ einfach aussehendes Problem, das ich versuche zu lösen. Es scheint keinen intuitiven Weg zu geben, oder ich vermisse etwas hier.Java 8 generische Sammlungen mit Optionals

Betrachten Sie diese Methode, um das Haupt-Bild zu finden und wenn keine vorhanden ist, kehrt erste 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()) 
} 

Ich erhalte eine Fehlermeldung - orElse(capture<? extends Image>) in Optional cannot be applied

Jede Richtung auf das wäre toll.

+3

Dies würde wahrscheinlich funktionieren, wenn es nur eine 'Collection ' wäre, aber die Art, wie Generika funktionieren, 'Optional .orElse' kann nur ein' T' akzeptieren. –

+0

Kannst du die Methodensignatur in 'public T findMainImage (Collection images) ändern' – Misha

+0

@misha Ich verstehe, wie es funktioniert, indem ich die Signatur ändere, ich bin mehr daran interessiert, die Gründe dahinter zu verstehen. – sanz

Antwort

7

Ein Weg, um es zu beheben ist ein Typ-Parameter zu verwenden:

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()); 
} 

Denn dann (an den Compiler) die Optional definitiv die gleiche Art Argument wie images hat.

Und wir könnten das als capturing helper verwenden, wenn wir wollten:

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

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

Persönlich würde ich nur die generische Version, denn dann können Sie tun, zB:

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

Grundsätzlich ... die Begründung dafür, warum es nicht ursprünglich kompiliert wird, ist, dich davon abzuhalten, so etwas zu tun:

Und im ursprünglichen Beispiel muss der Compiler nicht feststellen, dass sowohl Stream als auch Iterator vom gleichen Objekt stammen. Zwei separate Ausdrücke, die auf dasselbe Objekt verweisen, werden in zwei separaten Typen erfasst.

+1

Alternativ können Sie' Collections.unmodifiableCollection (images) .stream() verwenden. ... ' – Holger

2

Angenommen, Sie haben eine List<? extends Number>. Sie können keine Nummer zu dieser Liste hinzufügen, da es eine List<Integer> sein könnte und die Nummer, die Sie hinzufügen möchten, könnte eine Float sein.

Aus demselben Grund erfordert die Methode orElse(T t) von Optional<T> eine T. Da es sich bei dem Optional um einen Optional<? extends Image> handelt, kann der Compiler nicht sicher sein, dass images.iterator().next() vom richtigen Typ ist.

Ich habe diese, indem sie in .map(t -> (Image) t) zu kompilieren:

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

In der Tat, aus irgendeinem Grund, den ich nicht verstehen kann, es funktioniert auch ohne den Darstellern. Nur mit

.map(t -> t) 

scheint dies zu tun, funktioniert.

+1

ich gehe davon aus, dass Sie wirklich '' vor findFirst' .map' haben. die Art der 't' ist die obere Grenze der Sammlung,' Image' , egal, ob Sie "t" auf "Bild" oder nicht "" drehen, es ist ein "Bild", und der "Stream" kehrt zurück ed ist jetzt ein 'Stream ', anstatt 'Stream '. Das 'Optional' ist jetzt' Optional 'anstelle von' Optional ', und das erlaubt Ihnen, ein' Image' an 'orElse' zu ​​übergeben. – rgettman

+0

@gerttman 'Karte' kann in beiden Positionen gehen. 'Optional' hat auch eine Methode' map', also habe ich ein 'Optional 'auf ein' Optional '. –

+0

Ich wusste nicht einmal, dass 'Optional' auch eine' map' Methode hat. Es gibt das Sprichwort "Du lernst jeden Tag etwas Neues" und ich habe gerade etwas Neues gelernt. – rgettman