Dlaczego Iterable nie dostarcza metod stream () i parallelStream ()?

Zastanawiam się, dlaczego interfejs Iterable nie dostarcza metod stream() i parallelStream(). Rozważmy następującą klasę:

public class Hand implements Iterable<Card> {
    private final List<Card> list = new ArrayList<>();
    private final int capacity;

    //...

    @Override
    public Iterator<Card> iterator() {
        return list.iterator();
    }
}
Jest to implementacja układu , ponieważ możesz mieć w ręku karty podczas gry w karty kolekcjonerskie.

Zasadniczo owija List<Card>, zapewnia maksymalną pojemność i oferuje kilka innych przydatnych funkcji. Jest lepiej jako implementacja go bezpośrednio jako List<Card>.

Teraz, dla wygody myślałem, że to będzie miło zaimplementować Iterable<Card>, tak aby można było użyć rozszerzonych for-loopów, jeśli chcemy je zapętlić. (Moja klasa Hand dostarcza również metody get(int index), stąd {[8] } jest moim zdaniem uzasadniona.)

Interfejs Iterable zapewnia następujące (pominięte javadoc):

public interface Iterable<T> {
    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

Teraz możesz uzyskać strumień z:

Stream<Hand> stream = StreamSupport.stream(hand.spliterator(), false);

Więc na prawdziwe pytanie:

  • dlaczego Iterable<T> nie podaje domyślnych metod, które implementują stream() i parallelStream(), nie widzę nic, co mogłoby tego niemożliwe czy niepożądane?

Podobne pytanie, które znalazłem, jest następujące: dlaczego Streamnie implementuje Iterowalnego?
Co jest na tyle dziwne, że sugeruje to, aby zrobić to nieco na odwrót.

Author: Naman, 2014-04-16

3 answers

Nie było to pominięciem; w czerwcu 2013 r. przeprowadzono szczegółową dyskusję na temat listy EG.

Ostateczna dyskusja grupy ekspertów jest zakorzeniona w tym wątku .

Chociaż wydawało się "oczywiste" (nawet dla grupy ekspertów, początkowo), że stream() wydawało się mieć sens na Iterable, fakt, że Iterable był tak ogólny, stał się problemem, ponieważ oczywisty podpis: {]}

Stream<T> stream()
Nie zawsze tego chciałeś. Niektóre rzeczy, które były Iterable<Integer> na przykład ich metoda stream zwraca IntStream. Ale umieszczenie metody stream() tak wysoko w hierarchii byłoby niemożliwe. Zamiast tego ułatwiliśmy tworzenie Stream z Iterable, dostarczając metodę spliterator(). Implementacja stream() w Collection jest po prostu:
default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

Każdy klient może uzyskać żądany strumień z Iterable za pomocą:

Stream s = StreamSupport.stream(iter.spliterator(), false);

W końcu doszliśmy do wniosku, że dodanie stream() do Iterable byłoby błędem.

 301
Author: Brian Goetz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-22 16:02:08

Przeprowadziłem dochodzenie na kilku listach dyskusyjnych projektu lambda i myślę, że znalazłem kilka ciekawych dyskusji.

Jak dotąd nie znalazłem zadowalającego wyjaśnienia. Po przeczytaniu tego wszystkiego doszedłem do wniosku, że to tylko przeoczenie. Ale możesz zobaczyć tutaj, że był on omawiany kilka razy w ciągu lat podczas projektowania API.

Lambda Libs Spec Experts

Znalazłem dyskusję na ten temat w Lambda Libs Spec Experts mailingu lista :

Pod Iterable / Iterator.stream() sam Pullara powiedział:

Pracowałam z Brianem nad tym, jak limit / substream funkcjonalność [1] może być zaimplementowana i zaproponował konwersję do Iterator był dobrym rozwiązaniem. Myślałem o tym. rozwiązanie, ale nie znalazł żadnego oczywistego sposobu, aby wziąć iterator i włączyć do strumienia. Okazuje się, że jest tam, trzeba tylko najpierw przekształcić iterator na spliterator, a następnie Konwertuj spliterator do strumienia. Więc to sprowadza mnie do ponownego zastanowienia się, czy powinniśmy mieć te zwisające z jednego z Iterable / Iterator bezpośrednio lub oba.

Moja sugestia to przynajmniej mieć go na Iteratorze, więc można przenieść czysto między dwoma światami i byłoby to również łatwe odkrywalne zamiast robić:

Strumienie.strumień(bułg.spliteratorUnknownSize(iterator, Spliterator.Uporządkowane))

A potem Brian Goetz odpowiedź :

Myślę, że chodzi o to, że jest wiele klas bibliotecznych, które dać iteratora, ale nie pozwól koniecznie napisać własnego spliterator. Więc wszystko, co możesz zrobić, to zadzwonić stream(spliteratorUnknownSize (iterator)). Sam sugeruje, że zdefiniuj Iterator.stream (), aby zrobić to za Ciebie.

Chciałbym, aby metody stream() i spliterator() były dla autorów bibliotek / zaawansowanych użytkowników.

Oraz później

"biorąc pod uwagę, że pisanie Spliteratora jest łatwiejsze niż pisanie iteratora, Wolałbym po prostu napisać Spliterator zamiast iteratora (Iterator jest taki 90s :) "

Ale nie rozumiesz. Są miliony zajęć tam, żejuż dać Ci Iterator. A wielu z nich nie jest spliterator gotowy.

Poprzednie dyskusje na liście dyskusyjnej Lambda

To może nie być odpowiedź jesteś szukając ale w projekt lambda lista dyskusyjna to było krótko omówione. Być może przyczyni się to do szerszej dyskusji na ten temat.

W słowach Briana Goetza pod strumienie z Iterable :

Cofam się... Istnieje wiele sposobów tworzenia strumienia. Im więcej informacji mają o tym, jak opisać elementy, więcej funkcjonalności i wydajność, którą może Ci dać biblioteka strumieni. W kolejności najmniejszej na większość informacji to:

Iterator

Iterator + Rozmiar

Spliterator

Spliterator, który zna swój rozmiar

Spliterator, który zna swój rozmiar, a ponadto wie, że wszystkie sub-Splitty poznaj ich rozmiar.

(niektórzy mogą się dziwić, że możemy wyodrębnić równoległość nawet z durnego iteratora w przypadkach, gdy Q (praca na element) jest nietrywialne.)

Jeśli Iterable ma metodę stream (), to po prostu zawija Iterator z Spliterator, bez informacji o rozmiarze. Ale większość rzeczy, które są Iterabledo mają informacje o rozmiarze. Co oznacza, że serwujemy deficytowe strumienie. Nie za dobrze.

Jedną z wad praktyki API opisanej przez Stephena tutaj, z akceptując Iterable zamiast Collection, jest to, że wymuszasz rzeczy przez "małą rurę", a zatem odrzucenie wielkości informacje, kiedy mogą być przydatne. To dobrze, jeśli wszystko, co robisz na zrobić jest forEach to, ale jeśli chcesz zrobić więcej, to lepiej, jeśli może zachować wszystkie potrzebne informacje.

Domyślna wartość dostarczana przez Iterable byłaby naprawdę beznadziejna -- to odrzuciłoby rozmiar, mimo że zdecydowana większość Iterabli wie ta informacja.

Sprzeczność?

Chociaż wygląda na to, że dyskusja opiera się na zmianach, które grupa ekspertów zrobiła w początkowym projekcie strumieni, który początkowo opierał się na Iteratory.

Mimo to, warto zauważyć, że w interfejsie takim jak Collection, metoda stream jest zdefiniowana jako:

default Stream<E> stream() {
   return StreamSupport.stream(spliterator(), false);
}

Co może być dokładnie tym samym kodem używanym w iterowalnym interfejsie.

Dlatego powiedziałem, że ta odpowiedź prawdopodobnie nie jest zadowalająca, ale nadal ciekawa do dyskusji.

Dowód refaktoryzacji

Kontynuując analizę na liście dyskusyjnej, wygląda na to, że metoda splitIterator była początkowo w interfejsie kolekcji, a w pewnym momencie w 2013 roku przenieśli go do Iterable.

Pull splitIterator up from Collection to Iterable .

Wnioski/Teorie?

Wtedy są szanse, że brak metody w Iterable jest tylko pominięciem, ponieważ wygląda na to, że powinni byli przenieść metodę stream, jak również, gdy przenieśli splitIterator z kolekcji do Iterable.

Jeśli są inne powody, nie są one oczywiste. Ktoś jeszcze ma inne teorie?

 23
Author: Edwin Dalorzo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-20 14:53:00

Jeśli znasz rozmiar, możesz użyć java.util.Collection, który zapewnia metodę stream():

public class Hand extends AbstractCollection<Card> {
   private final List<Card> list = new ArrayList<>();
   private final int capacity;

   //...

   @Override
   public Iterator<Card> iterator() {
       return list.iterator();
   }

   @Override
   public int size() {
      return list.size();
   }
}

A następnie:

new Hand().stream().map(...)

Spotkałem się z tym samym problemem i byłem zaskoczony, że moja implementacja Iterable może być bardzo łatwo rozszerzona do AbstractCollection poprzez dodanie metody size() (na szczęście miałem rozmiar kolekcji: -)

Należy również rozważyć nadpisanie Spliterator<E> spliterator().

 6
Author: Udo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-30 20:11:09