2016-03-22 14 views
7

Sto trasferendo un pezzo di codice da .NET a Java e sono incappato in uno scenario in cui voglio utilizzare lo stream per mappare & ridurre.Java 8 stream join e restituisce più valori

class Content 
{ 
    private String propA, propB, propC; 
    Content(String a, String b, String c) 
    { 
    propA = a; propB = b; propC = c; 
    } 
    public String getA() { return propA; } 
    public String getB() { return propB; } 
    public String getC() { return propC; } 
} 

List<Content> contentList = new ArrayList(); 
contentList.add(new Content("A1", "B1", "C1")); 
contentList.add(new Content("A2", "B2", "C2")); 
contentList.add(new Content("A3", "B3", "C3")); 

Voglio scrivere una funzione che può flusso attraverso il contenuto di ContentList e restituire una classe con risultato

content { propA = "A1, A2, A3", propB = "B1, B2, B3", propC = "C1, C2, C3" } 

Sono abbastanza nuovo per Java per cui si potrebbe trovare qualche codice che assomiglia più come C# di java

+1

buona descrizione del problema. Ma ora, cosa hai provato? StackOverflow non è una comunità per far sì che altre persone scrivano il codice per te. Piuttosto, devi pubblicare un [mcve] che mostra ciò che hai provato, ciò che hai ottenuto e ciò che ti aspettavi, e la community di StackOverflow potrebbe cercare di aiutare a determinare dove sei andato storto e indicare soluzioni. – AJNeufeld

+0

@AJNeufeld ringrazia per il suggerimento. Questa è anche la prima volta che cerco di pubblicare una domanda su SO anche se la uso tutti i giorni. Ho a che fare con un ciclo di base con una variabile per aggiungere il contenuto. E ho provato ad usare forEach sullo stream e poi ho capito che le funzioni anonimi di Java non consentono la modifica della variabile all'interno del blocco foreach. Inoltre ho pensato di scrivere una raccolta per raggiungerla, ma ho capito che non sarebbe stata neanche efficiente. – Vedanth

risposta

3

È possibile utilizzare lambda appropriato per BinaryOperator in funzione di riduzione.

Content c = contentList 
      .stream() 
      .reduce((t, u) -> new Content(
            t.getA() + ',' + u.getA(), 
            t.getB() + ',' + u.getB(), 
            t.getC() + ',' + u.getC()) 
        ).get(); 
+1

Funziona, ma crea molti oggetti 'Contenuti' temporanei. – AJNeufeld

+1

@AJNeufeld concordato, ma è la scelta tra spazio e tempo. – mks

+1

Con solo 3 proprietà, sono parziale alla mia implementazione. Ma in un caso più generale - con più proprietà, proprietà più complesse, proprietà private, ecc. - il meglio potrebbe essere 'reduce ((t, u) -> nuovo Contenuto (t, u);' delegando la fusione a l'oggetto 'Content' stesso con un costruttore specializzato, o in realtà scrivendo un' ContentCollector' che accumula le informazioni in modo efficiente. – AJNeufeld

4
static Content merge(List<Content> list) { 
    return new Content(
      list.stream().map(Content::getA).collect(Collectors.joining(", ")), 
      list.stream().map(Content::getB).collect(Collectors.joining(", ")), 
      list.stream().map(Content::getC).collect(Collectors.joining(", "))); 
} 

EDIT: Ampliando collettore in linea di Federico, qui è una classe concreta dedicata alla fusione oggetti di contenuto:

class Merge { 

    public static Collector<Content, ?, Content> collector() { 
     return Collector.of(Merge::new, Merge::accept, Merge::combiner, Merge::finisher); 
    } 

    private StringJoiner a = new StringJoiner(", "); 
    private StringJoiner b = new StringJoiner(", "); 
    private StringJoiner c = new StringJoiner(", "); 

    private void accept(Content content) { 
     a.add(content.getA()); 
     b.add(content.getB()); 
     c.add(content.getC()); 
    } 

    private Merge combiner(Merge second) { 
     a.merge(second.a); 
     b.merge(second.b); 
     c.merge(second.c); 
     return this; 
    } 

    private Content finisher() { 
     return new Content(a.toString(), b.toString(), c.toString()); 
    } 
} 

Usato come:

Content merged = contentList.stream().collect(Merge.collector()); 
3

Se non si desidera ripetere l'iterazione 3 volte rispetto alla lis t, o non vogliono creare troppi Content oggetti intermedi, allora avresti bisogno di raccogliere il flusso con una propria implementazione:

public static Content collectToContent(Stream<Content> stream) { 
    return stream.collect(
     Collector.of(
      () -> new StringBuilder[] { 
        new StringBuilder(), 
        new StringBuilder(), 
        new StringBuilder() }, 
      (StringBuilder[] arr, Content elem) -> { 
       arr[0].append(arr[0].length() == 0 ? 
         elem.getA() : 
         ", " + elem.getA()); 
       arr[1].append(arr[1].length() == 0 ? 
         elem.getB() : 
         ", " + elem.getB()); 
       arr[2].append(arr[2].length() == 0 ? 
         elem.getC() : 
         ", " + elem.getC()); 
      }, 
      (arr1, arr2) -> { 
       arr1[0].append(arr1[0].length() == 0 ? 
         arr2[0].toString() : 
         arr2[0].length() == 0 ? 
           "" : 
           ", " + arr2[0].toString()); 
       arr1[1].append(arr1[1].length() == 0 ? 
         arr2[1].toString() : 
         arr2[1].length() == 0 ? 
           "" : 
           ", " + arr2[1].toString()); 
       arr1[2].append(arr1[2].length() == 0 ? 
         arr2[2].toString() : 
         arr2[2].length() == 0 ? 
           "" : 
           ", " + arr2[2].toString()); 
       return arr1; 
      }, 
      arr -> new Content(
        arr[0].toString(), 
        arr[1].toString(), 
        arr[2].toString()))); 
} 

Questo collettore prima crea un array di 3 vuote StringBuilder oggetti. Quindi definisce un accumulatore che aggiunge ogni proprietà dell'elemento Content allo StringBuilder corrispondente. Quindi definisce una funzione di unione che viene utilizzata solo quando il flusso viene elaborato in parallelo, che unisce due risultati parziali accumulati in precedenza. Infine, definisce anche una funzione di finitura che trasforma gli oggetti 3 StringBuilder in una nuova istanza di Content, con ogni proprietà corrispondente alle stringhe accumulate dei passaggi precedenti.

Si prega di controllare Stream.collect() e Collector.of() javadocs per ulteriori riferimenti.

4

Il modo più generico per affrontare tali compiti sarebbe quello di combinare il risultato di più collezionisti in uno solo.

Utilizzando la libreria jOOL, si potrebbe avere la seguente:

Content content = 
    Seq.seq(contentList) 
     .collect(
     Collectors.mapping(Content::getA, Collectors.joining(", ")), 
     Collectors.mapping(Content::getB, Collectors.joining(", ")), 
     Collectors.mapping(Content::getC, Collectors.joining(", ")) 
     ).map(Content::new); 

Questo crea un Seq dalla lista di input e combina le 3 date collezionisti di creare un Tuple3, che è semplicemente un supporto per 3 valori. Questi 3 valori vengono quindi mappati in un Content utilizzando il costruttore new Content(a, b, c). Gli stessi collector stanno semplicemente mappando ogni Content nel suo valore a, o c e unendo i risultati insieme separati con uno ", ".


Senza l'aiuto di terze parti, potremmo creare il nostro collettore combinatore come questo (questo è basato su StreamExpairing collettore, che fa la stessa cosa per 2 collettori). Prende 3 collector come argomenti ed esegue un'operazione di finisher sul risultato dei 3 valori raccolti.

public interface TriFunction<T, U, V, R> { 
    R apply(T t, U u, V v); 
} 

public static <T, A1, A2, A3, R1, R2, R3, R> Collector<T, ?, R> combining(Collector<? super T, A1, R1> c1, Collector<? super T, A2, R2> c2, Collector<? super T, A3, R3> c3, TriFunction<? super R1, ? super R2, ? super R3, ? extends R> finisher) { 

    final class Box<A, B, C> { 
     A a; B b; C c; 
     Box(A a, B b, C c) { 
      this.a = a; 
      this.b = b; 
      this.c = c; 
     } 
    } 

    EnumSet<Characteristics> c = EnumSet.noneOf(Characteristics.class); 
    c.addAll(c1.characteristics()); 
    c.retainAll(c2.characteristics()); 
    c.retainAll(c3.characteristics()); 
    c.remove(Characteristics.IDENTITY_FINISH); 

    return Collector.of(
      () -> new Box<>(c1.supplier().get(), c2.supplier().get(), c3.supplier().get()), 
      (acc, v) -> { 
       c1.accumulator().accept(acc.a, v); 
       c2.accumulator().accept(acc.b, v); 
       c3.accumulator().accept(acc.c, v); 
      }, 
      (acc1, acc2) -> { 
       acc1.a = c1.combiner().apply(acc1.a, acc2.a); 
       acc1.b = c2.combiner().apply(acc1.b, acc2.b); 
       acc1.c = c3.combiner().apply(acc1.c, acc2.c); 
       return acc1; 
      }, 
      acc -> finisher.apply(c1.finisher().apply(acc.a), c2.finisher().apply(acc.b), c3.finisher().apply(acc.c)), 
      c.toArray(new Characteristics[c.size()]) 
      ); 
} 

e, infine, lo usano con

Content content = contentList.stream().collect(combining(
    Collectors.mapping(Content::getA, Collectors.joining(", ")), 
    Collectors.mapping(Content::getB, Collectors.joining(", ")), 
    Collectors.mapping(Content::getC, Collectors.joining(", ")), 
    Content::new 
));