2016-03-23 16 views
6

Ho 2 classi Java.Come ottenere un elenco distinto di oggetti con concat in Java 8

class A { 
String name; 
List<B> numbers; 
} 

class B { 
Integer number; 
} 

Voglio ottenere il distinto di Classe A e concatenare l'Elenco di B in esso.

ad es. Supponiamo di avere una lista con i seguenti oggetti.

List<A>{ 
name = "abc" 
List<B>{1,2} 

name= "xyz" 
List<B>{3,4} 

name = "abc" 
List<B>{3,5} 
} 

Il risultato dovrebbe essere:

List<A>{ 
name = "abc" 
List<B>{1,2,3,5} 

name="xyz" 
List<B>{3,4} 
} 

Qualsiasi aiuto sarebbe apprezzato.

Nota: Voglio ottenere questa funzionalità utilizzando Java 8 stream.

Grazie

+0

@PaulBoddington Sì Sto creando la nuova istanza di classe A con i valori del database e aggiungendola all'elenco. Una volta che la lista è popolata. Ho bisogno di ottenere oggetto distinto di classe A con l'elenco concatenato di classe B. –

+1

Grazie. Ho cancellato il commento perché a prima vista sembrava che 'A' e' B' fossero pseudocodice ma poi si sono resi conto che sono classi complete che compilano. –

risposta

3

È possibile utilizzare toMap collezionista:

Collection<A> result = list.stream() 
     .collect(Collectors.toMap(a -> a.name, a -> a, 
         (a, b) -> {a.numbers.addAll(b.numbers); return a;})) 
     .values(); 

È possibile copiare il risultato dopo che in List (come new ArrayList<>(result)), ma come noi non conserviamo alcun ordine particolare, avendo List non è molto utile. Nella maggior parte degli scenari con Collection di conseguenza va bene.

+3

È possibile conservare l'ordine utilizzando l'overload di quattro argomenti di 'toMap()' e passando 'LinkedHashMap :: new' come' Fornitore'. – jaco0646

+0

Per me fornisce 'java.lang.UnsupportedOperationException' alla riga 39: http://pastebin.com/fAvd6jMi – ctomek

+0

@ctomek, le liste sono create come' Arrays.asList' che non supportano l'aggiunta di nuovi elementi. Questo potrebbe essere facilmente risolto usando 'ArrayList'. –

1

Ecco la mia risposta. Ho aggiunto un costruttore per A per renderlo leggermente più semplice.

public class Main { 

    static class A { 
     String name; 
     List<B> numbers; 

     A(String name, List<B> numbers) { 
      this.name = name; 
      this.numbers = new ArrayList<>(numbers); 
     } 
    } 

    static class B { 
     Integer number; 
    } 

    static List<A> merge(List<A> list) { 
     Map<String, List<B>> map = new LinkedHashMap<>(); 
     for (A a : list) 
      map.computeIfAbsent(a.name, k -> new ArrayList<>()).addAll(a.numbers); 
     return map.entrySet() 
        .stream() 
        .map(e -> new A(e.getKey(), e.getValue())) 
        .collect(Collectors.toList()); 
    } 
} 

non v'è quasi certamente un modo semplice per sostituire le prime 3 righe del metodo merge con qualcosa che inizia list.stream()... rendendo l'intero metodo di un one-liner. Non ero in grado di risolverlo però. Forse qualcun altro può modificare questa risposta mostrando come?

2

Non penso che ci sia un modo per aggirare la mappa. È tuttavia possibile utilizzare groupingBy per nasconderlo, ma è effettivamente lo stesso codice e le stesse prestazioni suggerite da Paul Boddington.

List<A> merge(List<A> input) { 
    return input.stream() 
      .collect(groupingBy(a -> a.name)) // map created here 
      .entrySet() 
      .stream() 
      .map(entry -> new A(
        entry.getKey(), 
        entry.getValue().stream() 
          .flatMap(list -> list.numbers.stream()) 
          .collect(toList()) // merging behaviour 
      )).collect(toList()); 
} 

Non v'è alcuna mutazione della lista originale e si può facilmente cambiare il comportamento di fusione delle liste - per esempio se si vuole sbarazzarsi di duplicati basta aggiungere .distinct() dopo flatMap(list -> list.numbers.stream()) (ricordate di aggiungere equals a B) o in modo simile è possibile ordinarli semplicemente aggiungendo .sorted() (si deve fare in modo che B attui l'interfaccia Comparable o usi semplicemente .sorted(Comparator<B>)).

Qui è pieno di codice con i test e le importazioni:

import org.junit.Test; 

import java.util.List; 

import static com.shazam.shazamcrest.MatcherAssert.assertThat; 
import static com.shazam.shazamcrest.matcher.Matchers.sameBeanAs; 
import static java.util.Arrays.asList; 
import static java.util.stream.Collectors.groupingBy; 
import static java.util.stream.Collectors.toList; 

public class Main { 

    class A { 
     final String name; 
     final List<B> numbers; 
     A(String name, List<B> numbers) { 
      this.name = name; 
      this.numbers = numbers; 
     } 
    } 

    class B { 
     final Integer number; 
     B(Integer number) { 
      this.number = number; 
     } 
    } 

    List<A> merge(List<A> input) { 
     return input.stream() 
       .collect(groupingBy(a -> a.name)) 
       .entrySet() 
       .stream() 
       .map(entry -> new A(
         entry.getKey(), 
         entry.getValue().stream() 
           .flatMap(list -> list.numbers.stream()) 
           .collect(toList()) 
       )).collect(toList()); 
    } 

    @Test 
    public void test() { 
     List<A> input = asList(
       new A("abc", asList(new B(1), new B(2))), 
       new A("xyz", asList(new B(3), new B(4))), 
       new A("abc", asList(new B(3), new B(5))) 
     ); 

     List<A> list = merge(input); 

     assertThat(list, sameBeanAs(asList(
       new A("abc", asList(new B(1), new B(2), new B(3), new B(5))), 
       new A("xyz", asList(new B(3), new B(4))) 
     ))); 
    } 

} 

EDIT:

Seguendo le vostre domande nei commenti, se si desidera aggiungere più campi in groupingBy clausola, si avrebbe bisogno di crea una classe che rappresenta una chiave nella mappa. Se hai campi che non vuoi includere nella chiave, devi definire come unire due valori diversi, in modo simile a ciò che fai con i numeri. A seconda di quali sono i campi, il comportamento di unione può essere semplicemente scegliere il primo valore e scartare l'altro (cosa ho fatto con numbers nel codice qui sotto).

class A { 
    final String name; 
    final String type; 
    final List<B> numbers; 
    A(String name, String type, List<B> numbers) { 
     this.name = name; 
     this.type = type; 
     this.numbers = numbers; 
    } 
} 

class B { 
    final Integer number; 
    B(Integer number) { 
     this.number = number; 
    } 
} 

class Group { 
    final String name; 
    final String type; 
    Group(String name, String type) { 
     this.name = name; 
     this.type = type; 
    } 
    // this needs to have equals and hashCode methods as we use it as a key in a map 
} 

List<A> merge(List<A> input) { 
    return input.stream() 
      .collect(groupingBy(a -> new Group(a.name, a.type))) 
      .entrySet() 
      .stream() 
      .map(entry -> new A(
        entry.getKey().name, // entry.getKey() is now Group, not String 
        entry.getKey().type, 
        entry.getValue().get(0).numbers // no merging, just take first 
      )).collect(toList()); 
} 
+0

Ciao, grazie per la tua risposta. Posso aggiungere più campi nella clausola grouping? Se sì, quale sarebbe la sintassi? –

+0

La tua soluzione funzionerà se la classe A ha più campi ma non dovrebbe aggiungersi al raggruppamento per istruzione? –

Problemi correlati