2016-03-23 13 views
6

Ich habe 2 Java-Klassen.So erhalten Sie verschiedene Liste von Objekt mit Concat in Java 8

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

class B { 
Integer number; 
} 

Ich möchte die distinct von Klasse A und concat die Liste von B drin.

z.B. Angenommen, ich habe eine Liste mit den folgenden Objekten.

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

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

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

sollte das Ergebnis sein:

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

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

Jede Hilfe würde geschätzt.

Hinweis: Ich möchte diese Funktionalität mit Java 8 Streams erreichen.

Dank

+0

@PaulBoddington Ja Ich erstelle die neue Klasse A-Instanz mit den Werten aus der Datenbank und füge sie in die Liste ein. Sobald die Liste ausgefüllt ist. Ich brauche distinct Objekt der Klasse A mit verketteten Liste der Klasse B. –

+1

Danke. Ich habe den Kommentar gelöscht, weil es auf den ersten Blick so aussah, als wären 'A' und' B' Pseudocode, aber dann wurde klar, dass es sich um vollständige Klassen handelt, die kompilieren. –

Antwort

3

Sie toMap Sammler verwenden:

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

Sie können das Ergebnis danach in List (wie new ArrayList<>(result)) kopieren, aber da wir keine bestimmte Reihenfolge erhalten, mit List nicht sehr ist sinnvoll. In den meisten Szenarien mit Collection als Ergebnis ist in Ordnung.

+3

Sie können die Reihenfolge beibehalten, indem Sie die Überladung mit vier Argumenten von 'toMap()' verwenden und 'LinkedHashMap :: new' als' Supplier' übergeben. – jaco0646

+0

Für mich gibt es 'java.lang.UnsupportedOperationException' in Zeile 39: http://pastebin.com/fAvd6jMi – ctomek

+0

@ctomek, Ihre Listen werden als' Arrays.asList' erstellt, die das Hinzufügen neuer Elemente nicht unterstützen. Dies könnte einfach mit 'ArrayList' behoben werden. –

1

Hier ist meine Antwort. Ich habe einen Konstruktor für A hinzugefügt, um es etwas einfacher zu machen.

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()); 
    } 
} 

Es ist mit ziemlicher Sicherheit eine einfache Möglichkeit, die ersten 3 Zeilen der merge Methode mit etwas beginnen list.stream()... macht das gesamte Verfahren eine Einzeiler zu ersetzen. Ich konnte es jedoch nicht ausarbeiten. Vielleicht kann jemand anders diese Antwort bearbeiten, die zeigt, wie?

2

Ich glaube nicht, dass es einen Weg gibt, um die Karte zu umgehen. Sie können jedoch groupingBy verwenden, um es zu verbergen, aber es ist effektiv der gleiche Code und die gleiche Leistung wie Paul Boddington vorgeschlagen. keine Mutation der ursprünglichen Liste

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()); 
} 

Es gibt und man kann leicht das Verhalten ändern, um die Listen der Zusammenführung - zum Beispiel, wenn Sie Duplikate loswerden wollen fügen Sie einfach .distinct() nach flatMap(list -> list.numbers.stream()) (Sie erinnern sich über equals zu B Zugabe) oder Auf ähnliche Weise können Sie sie sortieren, indem Sie einfach .sorted() hinzufügen (Sie müssen B implementieren Comparable Schnittstelle oder einfach .sorted(Comparator<B>) verwenden).

Hier vollständigen Code mit Tests und Importen ist:

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:

Ihre Fragen in den Kommentaren Folgen, wenn Sie mehrere Felder in groupingBy Klausel hinzufügen möchten, müssen Sie Erstellen Sie eine Klasse, die einen Schlüssel in der Karte darstellt. Wenn Sie Felder haben, die Sie nicht in den Schlüssel aufnehmen möchten, müssen Sie definieren, wie zwei verschiedene Werte zusammengeführt werden - ähnlich wie bei Zahlen. Je nachdem, was die Felder sind, kann das Merging-Verhalten nur den ersten Wert auswählen und den anderen verwerfen (was ich im folgenden Code mit numbers gemacht habe).

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

Hallo, Danke für deine Antwort. Kann ich mehrere Felder in der groupingBy-Klausel hinzufügen? Wenn ja, wie wäre die Syntax? –

+0

Würde Ihre Lösung funktionieren, wenn Klasse A mehr Felder enthält, aber nicht durch Anweisungen gruppieren soll? –