2016-04-29 12 views
34

Sappiamo che Java 8 introduce una nuova API Stream e java.util.stream.Collector è l'interfaccia per definire come aggregare/raccogliere il flusso di dati.Perché la classe Java 'Collector' è progettata in questo modo?

Tuttavia, l'interfaccia di raccolta è stato progettato in questo modo:

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

Perché non è stato progettato come la seguente?

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

Quest'ultimo è molto più facile da implementare. Quali erano le considerazioni per progettarlo come il precedente?

+0

è tutto basato su OOP e struttura coerente, semplicemente a indovinare. – PSo

+3

Si noti che è possibile implementare una classe base astratta che si adatta al secondo modello. – Thilo

+0

@Thilo Penso che sia il primo che il secondo modello possono essere adattati in ogni direzione. Penso solo che il secondo è più intuitivo. – popcorny

risposta

26

In realtà è stato originariamente progettato in modo simile a quello che proponete. Vedere the early implementation nel repository lambda del progetto (makeResult è ora supplier). È stato successivamente updated al design attuale. Credo che la logica di tale aggiornamento sia semplificare i combinatori di collettori. Non ho trovato alcuna discussione specifica su questo argomento, ma la mia ipotesi è supportata dal fatto che il collezionista mapping è apparso nello stesso changeset. Prendere in considerazione l'attuazione di 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()); 
} 

Questa implementazione deve ridefinire solo accumulator funzione, lasciando supplier, combiner e finisher come è, in modo da non avere ulteriore riferimento indiretto al momento della chiamata supplier, combiner o finisher: basta chiamare direttamente le funzioni restituite dal raccoglitore originale. E 'ancora più importante con 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); 
} 

Qui è cambiato solo finisher, ma originale supplier, accumulator e combiner vengono utilizzati. Come viene chiamato accumulator per ogni elemento, la riduzione dell'indirizzamento potrebbe essere piuttosto importante. Prova a riscrivere mapping e collectingAndThen con il tuo progetto proposto e vedrai il problema. Anche i nuovi collettori JDK-9 come filtering e flatMapping beneficiano del design attuale.

18

Composition is favored over inheritance.

Il primo modello nella tua domanda è una sorta di configurazione del modulo. Le implementazioni dell'interfaccia di Collector possono fornire implementazioni diverse per Fornitore, Accumulatore, ecc. Ciò significa che è possibile comporre le implementazioni del Collector da un pool esistente di implementazioni Fornitore, Accumulatore, ecc. Questo aiuta anche a riutilizzare e due Collector potrebbero usare la stessa implementazione di Accumulator. Il Stream.collect() utilizza i comportamenti forniti.

Nel secondo schema, l'implementazione di Collector deve implementare tutte le funzioni da sola. Tutti i tipi di variazioni avrebbero bisogno di sovrascrivere l'implementazione genitore. Non c'è molto spazio per il riutilizzo, oltre alla duplicazione del codice se due collettori hanno una logica simile per un passo, ad esempio l'accumulo.

+0

Grazie per la risposta. Ma per la mia versione, è anche facile implementare una versione wrapper helper di Collector.of (....) da queste funzioni lambda. – popcorny

0

2 motivi relativi

  • composizione funzionale tramite combinatori.(Nota: si può ancora fare la composizione OO ma a guardare di sotto del punto)
  • Possibilità di inquadrare la logica di business in codice espressivo succinta tramite espressione lambda o metodo di riferimento quando destinazione di assegnazione è un'interfaccia funzionale .

    composizione funzionale

    collezionisti API apre un modo per composizione funzionale tramite combinatori .i.e. costruisci piccole/piccole funzionalità riutilizzabili e combina alcune di queste spesso in modo interessante in una funzione/funzione avanzata.

    codice espressivo succinta

    seguito usando la funzione puntatore (Employee :: getSalary) per riempire la funzionalità di mapping da oggetto Employee a int. summingInt riempie la logica dell'aggiunta di interi e quindi combinati insieme abbiamo una somma di salari scritti in una singola riga di codice dichiarativo.

    // Calcola somma delle retribuzioni dei dipendenti int totale = employees.stream() .Raccogliere (Collectors.summingInt (Employee :: getSalary)));

Problemi correlati