2014-06-14 14 views
7

W java 8, java.util.stream.Stream#forEach należy rozważyć zastąpienie tradycyjnej pętli for. Ale dlaczego nie jest to funkcja łańcuchowa. Zwraca ona pustkę zamiast Stream<T>.Dlaczego nie łańcuch java.util.stream.Stream # forEach

Ci się to

Arrays 
    .stream(Girls.toArray()) 
    .forEach(Girls::getUp) 
    .forEach(Girls::dressUp) 
    .filter(/* Top 10 Girls */) 
    .forEach(Gay.getMe()::gotGirl) 
    .endFilter()// Not an API, but it means remove last filter 
    .filter(/* Worst 10 Girls */) 
    .forEach(Gay.get(0)::gotGirl) 


girls = Arrays 
    .stream(Girls.toArray()); 
girls.forEach(g->{g.getUp();g.dressUp()}); 
girls.filter(/* Top 10 Girls */) 
    .forEach(Gay.getMe()::gotGirl); 
girls.filter(/* Worst 10 Girls */) 
    .forEach(Gay.get(0)::gotGirl); 

pierwszy jest ładny niż drugi one.But pierwszy dostał gorszą wydajność.

Dlaczego zatem forEach nie nadaje się do kierowania?

Odpowiedz

11

Ponieważ forEach jest operacją terminalową. Zmusza strumień do zużywania wszystkich jego elementów i wywoływania konsumenta dla każdego z nich. Po wykonaniu tej czynności strumień zostanie zużyty i nie będzie można go ponownie wykorzystać.

Strumień ma tyle operacji pośrednich, ile chcesz, ale może mieć tylko jedną operację terminalu.

18

Metody, których szukasz, są nazwane Stream::peek i Stream::map. Z Stream::peek powyższy kod może wyglądać następująco.

Arrays 
    .stream(Girls.toArray()) 
    .peek(Girls::getUp) 
    .peek(Girls::dressUp) 
    .filter(/* Top 10 Girls */) 
    .peek(Gay.getMe()::gotGirl) 
    ... 
+10

jednak pamiętać, że mające skutki uboczne w 'peek()' lub 'mapie()' parametry behawioralne, które mogą kolidować ze źródła, lub wpływa na wynik innych operacji strumieniowych, jest nie-nie. Metoda "peek()" naprawdę służy tylko do debugowania/rejestrowania, a nie do obliczania wyniku; podobnie, 'map()' powinno być używane tylko do transformacji, a nie do ukrywania efektów ubocznych. –

5

Operacja forEach jest faktycznie operacją zacisk a zatem nie chainable, ale jest możliwe, aby utworzyć kilka operacji (konsumentów, w tym przypadku) w jednym forEach połączenia. Na przykład:

Consumer<String> c1 = s -> System.out.println(s + "1"); 
    Consumer<String> c2 = s -> System.out.println(s + "2"); 
    Consumer<String> c3 = s -> System.out.println(s + "3"); 
    Arrays.asList("a", "b", "c") 
      .stream() 
      .forEach(c1.andThen(c2).andThen(c3)); 

Niestety nie wydaje się możliwe zapisywanie linii lambda bez brzydkiego rzucania.

2

Chaining z andThen rzeczywiście jest droga, ale musimy trochę cukru składniowej, aby go dość:

public class Consumers { 
    public static <T> Consumer<T> of(Consumer<T> base) { 
     return base; 
    } 
} 

Pozwala nam to zrobić:

Arrays.asList("a", "b", "c") 
     .stream() 
     .forEach(Consumers.of(s -> System.out.println(s + "1")) 
         .andThen(s -> System.out.println(s + "2")) 
         .andThen(s -> System.out.println(s + "3"))); 

Albo (krótsza):

Arrays.asList("a", "b", "c") 
     .forEach(Consumers.of(s -> System.out.println(s + "1")) 
         .andThen(s -> System.out.println(s + "2")) 
         .andThen(s -> System.out.println(s + "3"))); 

(od forEach jest dostępny bezpośrednio u kolegi ction)

3

Kontynuując Stuart Marks' answer, możliwe jest, aby napisać inline lambdas ale potrzebuje tylko pierwszy konsument być wrzuconym:

Arrays.asList("a", "b", "c").forEach(
    ((Consumer<String>) s -> System.out.println(s + "1")) 
    .andThen(s->System.out.println(s + "2")) 
    .andThen(s -> System.out.println(s + "3")) 
); 
+2

Za każdym razem, gdy robisz wyraźną obsadę, umiera wróżka. – marthursson

0

Jak andThen jest zdefiniowane w Java 8 jako

default Consumer<T> andThen(Consumer<? super T> after) { 
    Objects.requireNonNull(after); 
    return (T t) -> { accept(t); after.accept(t); }; 
} 

możesz z łatwością utworzyć własny kompilator Consumer<T> dla dowolnej liczby swoich funkcji

class CompositeConsumer<T> implements Consumer<T> { 
    private final List<Consumer<T>> funcs; 

    CompositeConsumer(List<Consumer<T>> funcs) { 
     this.funcs = funcs; 
    } 

    @Override 
    public void accept(T t) { 
     for (Consumer<T> func: funcs) { 
      func.accept(t); 
     } 
    } 
} 

i użyć go do dowolnej liczby swoich funkcji na raz

List<Consumer<String>> funcs = ... 
Arrays.asList("a", "b", "c") 
     .forEach(new CompositeConsumer(funcs)); 
Powiązane problemy