2015-04-24 21 views
5

Mam stosunkowo prosty problem, który próbuję rozwiązać. Wydaje się, że nie ma intuicyjnego sposobu, aby to zrobić, lub czegoś tu brakuje.Java 8 ogólnych kolekcji z opcjami

Rozważmy tę metodę w celu znalezienia główny obraz i jeśli żaden nie istnieje, zwraca pierwszy Obraz-

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

pojawia się błąd - orElse(capture<? extends Image>) in Optional cannot be applied

dowolnym kierunku na to byłoby świetnie.

+3

Prawdopodobnie działałoby to, gdyby była to tylko 'Kolekcja ', ale sposób działania generyków, 'Opcjonalnie .orElse' może akceptować tylko' T'. –

+0

Czy możesz zmienić podpis metody na "publiczny T findMainImage (kolekcja )' – Misha

+0

@misha Dostaję jak działa, zmieniając podpis, jestem bardziej zainteresowany zrozumieniem tego, co za tym stoi. – sanz

Odpowiedz

7

Jednym ze sposobów, aby to naprawić jest użycie parametr typu:

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

Bo wtedy (kompilator) Optional pewno ma ten sam typ argumentu jako images.

I moglibyśmy użyć jej jako capturing helper jeśli chcieliśmy:

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

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

Osobiście, chciałbym po prostu użyć wersji rodzajowy, bo wtedy można zrobić np:

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

Zasadniczo ... powodem, dla którego nie kompiluje się oryginalnie, jest powstrzymanie cię przed zrobieniem czegoś takiego:

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

I w oryginalnym przykładzie kompilator nie musi określać faktu, że zarówno Stream, jak i Iterator pochodzą z tego samego obiektu. Dwa oddzielne wyrażenia odwołujące się do tego samego obiektu zostają przechwycone do dwóch oddzielnych typów.

+1

Alternatywnie, możesz użyć' Collections.unmodifiableCollection (images) .stream(). ... ' – Holger

2

Załóżmy, że masz List<? extends Number>. Nie można dodać dowolnej liczby do tej listy, ponieważ może to być List<Integer>, a liczba, którą próbujesz dodać, może wynosić Float.

Z tego samego powodu metoda orElse(T t) z Optional<T> wymaga T. Ponieważ dany Optional to Optional<? extends Image>, kompilator nie może być pewien, że images.iterator().next() jest właściwego typu.

mam to skompilować poprzez umieszczenie w .map(t -> (Image) t):

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

W rzeczywistości, z jakiegoś powodu nie mogę zrozumieć, że działa nawet bez obsady. Wydaje się, że to działa.

+1

Zakładam, że naprawdę masz '.map' przed' findFirst' Typ 't' to górna granica kolekcji,' Image' , więc czy rzucisz 't' na' Image' czy nie', to jest 'Obraz' i powrót' Stream' ed jest teraz 'Stream ', zamiast 'Stream '. Opcjonalnie 'Opcjonalnie' jest teraz Opcjonalne ' zamiast 'Opcjonalne ', a to pozwala ci przekazać' Image' do 'orElse'. – rgettman

+0

@rgettman 'map' może przejść w obu pozycjach. 'Opcjonalne' ma również metodę' map', więc zmapowałem opcję 'Opcjonalnie 'do' Opcjonalnie '. –

+0

Nie zdawałem sobie sprawy, że 'Opcjonalnie' ma również metodę' map'. Jest takie powiedzenie: "Każdego dnia uczysz się czegoś nowego", a ja nauczyłem się czegoś nowego. – rgettman

Powiązane problemy