2016-04-29 10 views
34

Wiemy, że Java 8 wprowadza nowy Stream API, a java.util.stream.Collector to interfejs do definiowania sposobu agregowania/zbierania strumienia danych.Dlaczego klasa "Collector" języka Java 8 została zaprojektowana w ten sposób?

Jednak interfejs kolektora jest zaprojektowany tak:

public interface Collector<T, A, R> { 
    Supplier<A> supplier(); 
    BiConsumer<A, T> accumulator(); 
    BinaryOperator<A> combiner(); 
    Function<A, R> finisher(); 
} 

Dlaczego nie jest on zaprojektowany tak jak poniżej?

public interface Collector<T, A, R> { 
    A supply(); 
    void accumulate(A accumulator, T value); 
    A combine(A left, A right); 
    R finish(A accumulator); 
} 

Ten drugi jest znacznie łatwiejszy do wdrożenia. Jakie były rozważania nad zaprojektowaniem go jako poprzedniego?

+0

to wszystko na temat OOP i struktury spójne, po prostu zgadywanie. – PSo

+3

Należy pamiętać, że można zaimplementować abstrakcyjną klasę bazową, która dostosowuje się do drugiego wzorca. – Thilo

+0

@Thilo Myślę, że zarówno pierwszy, jak i drugi wzór można dostosować w każdym kierunku. Po prostu uważam, że drugi jest bardziej intuicyjny. – popcorny

Odpowiedz

26

W rzeczywistości został pierwotnie zaprojektowany podobnie do tego, co proponujesz. Zobacz the early implementation w repozytorium lambda projektu (makeResult jest teraz supplier). To było później updated do obecnego projektu. Uważam, że uzasadnieniem takiej aktualizacji jest uproszczenie systemów gromadzenia kolektorów. Nie znalazłem żadnej konkretnej dyskusji na ten temat, ale moje przypuszczenie jest poparte faktem, że kolekcjoner mapping pojawił się w tym samym zestawie zmian. Rozważmy realizację Collectors.mapping:

public static <T, U, A, R> 
Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper, 
          Collector<? super U, A, R> downstream) { 
    BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator(); 
    return new CollectorImpl<>(downstream.supplier(), 
           (r, t) -> downstreamAccumulator.accept(r, mapper.apply(t)), 
           downstream.combiner(), downstream.finisher(), 
           downstream.characteristics()); 
} 

Ta implementacja musi przedefiniować tylko accumulator funkcji, pozostawiając supplier, combiner i finisher jak jest, więc nie ma dodatkowych zadnie Dzwoniąc supplier, combiner lub finisher: wystarczy zadzwonić bezpośrednio funkcje zwrócone przez oryginalny kolektor. To nawet ważniejsze z collectingAndThen:

public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream, 
                  Function<R,RR> finisher) { 
    // ... some characteristics transformations ... 
    return new CollectorImpl<>(downstream.supplier(), 
           downstream.accumulator(), 
           downstream.combiner(), 
           downstream.finisher().andThen(finisher), 
           characteristics); 
} 

Tutaj tylko finisher zostanie zmieniony, ale oryginalny supplier, accumulator i combiner są używane. Ponieważ dla każdego elementu wywoływane jest accumulator, zmniejszenie indoktrysu może być dość ważne. Spróbuj przepisać mapping i collectingAndThen z proponowanym projektem, a zobaczysz problem. Nowe kolektory JDK-9, takie jak filtering i flatMapping, również korzystają z obecnego projektu.

18

Composition is favored over inheritance.

Pierwszy wzór w swoim pytaniu jest rodzaj konfiguracji modułu. Implementacje interfejsu Collector mogą zapewnić różne implementacje dla Dostawcy, Akumulatora itp. Oznacza to, że można komponować implementacje kolektorów z istniejącej puli implementacji Dostawca, Akumulator itp. Pomaga to również w ponownym użyciu, a dwóch kolekcjonerów może korzystać z tej samej implementacji akumulatora. The Stream.collect() używa dostarczonych zachowań.

W drugim schemacie implementacja modułu Collector musi sama implementować wszystkie funkcje. Wszystkie rodzaje zmian wymagałyby nadrzędnej implementacji. Nie ma wiele możliwości ponownego wykorzystania, a także powielania kodu, jeśli dwa kolektory mają podobną logikę dla danego etapu, na przykład akumulację.

+0

Dzięki za odpowiedź. Ale dla mojej wersji, jest również łatwo zaimplementować wersję opakowania Collector.of (....) przez te funkcje lambda. – popcorny

0

2 związane powodów

  • kompozycja poprzez złożone jest funkcjonalna.(Uwaga nadal można zrobić kompozycję OO ale spojrzeć poniżej punktu)
  • Możliwość oprawy logikę biznesową w zwięzły kod wyraziste poprzez wyrażenia lambda lub metody odniesienia, gdy cel cesja jest funkcjonalny interfejs.

    kompozycja funkcjonalna

    Kolektory API otwiera drogę dla kompozycji funkcjonalną poprzez złożone jest .i.e. buduj małą/najmniejszą funkcjonalność wielokrotnego użytku i łącz niektóre z nich często w ciekawy sposób z zaawansowaną funkcją/funkcją.

    zwięzły kod wyraziste

    Poniżej używamy funkcji wskaźnika (Employee :: getSalary) wypełnić funkcjonalność odwzorowującym Address pracownika do int. summingInt wypełnia logikę dodawania intów, a co za tym idzie łączymy sumę wynagrodzeń zapisaną w jednym wierszu kodu deklaratywnego.

    // Oblicz sumę wynagrodzeń pracowników int całkowita = employees.stream() .collect (Collectors.summingInt (Pracownik :: getSalary)));

Powiązane problemy