Wie kann ich eine Liste von Listen in eine Liste in Java 8 verwandeln?

532

Wenn ich eine habe List<List<Object>>, wie kann ich daraus eine machen List<Object>, die alle Objekte in derselben Iterationsreihenfolge enthält, indem ich die Funktionen von Java 8 verwende?

Sarah Szabo
quelle

Antworten:

950

Sie können flatMapdie internen Listen (nachdem Sie sie in Streams konvertiert haben) in einen einzelnen Stream reduzieren und das Ergebnis dann in einer Liste zusammenfassen:

List<List<Object>> list = ...
List<Object> flat = 
    list.stream()
        .flatMap(List::stream)
        .collect(Collectors.toList());
Eran
quelle
11
@arshajii stimmt, aber aus irgendeinem Grund bevorzuge ich den Lambda-Ausdruck. Vielleicht mag ich das Aussehen von :::)
Eran
25
Class::methodfühlt sich anfangs etwas komisch an, hat aber den Vorteil, dass es deklariert, von welcher Art von Objekt Sie ein Mapping durchführen. Das ist etwas, was Sie sonst in Streams verlieren.
ArneHugo
2
Möglicherweise möchten Sie jedoch einen Container mit einer Liste haben. In diesem Fall müssen Sie möglicherweise das Lambda (l-> l.myList.stream ()) verwenden.
Myoch
2
Wenn Sie explizites Casting durchführen müssen (z. B. eine Reihe von Grundelementen für List), sind möglicherweise auch Lambdas erforderlich.
Michael Fulton
52

flatmap ist besser, aber es gibt andere Möglichkeiten, das gleiche zu erreichen

List<List<Object>> listOfList = ... // fill

List<Object> collect = 
      listOfList.stream()
                .collect(ArrayList::new, List::addAll, List::addAll);
Saravana
quelle
37

Die flatMapMethode on Streamkann diese Listen sicherlich für Sie reduzieren, muss jedoch StreamObjekte für das Element und dann a Streamfür das Ergebnis erstellen .

Sie brauchen nicht alle diese StreamObjekte. Hier ist der einfache, präzise Code zur Ausführung der Aufgabe.

// listOfLists is a List<List<Object>>.
List<Object> result = new ArrayList<>();
listOfLists.forEach(result::addAll);

Da a Listis ist Iterable, ruft dieser Code die forEachMethode (Java 8-Funktion) auf, von der geerbt wird Iterable.

Führt die angegebene Aktion für jedes Element von aus, Iterablebis alle Elemente verarbeitet wurden oder die Aktion eine Ausnahme auslöst. Aktionen werden in der Reihenfolge der Iteration ausgeführt, wenn diese Reihenfolge angegeben ist.

Und a List's Iteratorgibt Artikel in sequentieller Reihenfolge zurück.

Für den ConsumerCode wird eine Methodenreferenz (Java 8-Funktion) an die Methode vor Java 8 übergeben List.addAll, um die inneren Listenelemente nacheinander hinzuzufügen.

Hängt alle Elemente in der angegebenen Sammlung an das Ende dieser Liste in der Reihenfolge an, in der sie vom Iterator der angegebenen Sammlung zurückgegeben werden (optionale Operation).

rgettman
quelle
3
Guter alternativer Vorschlag, der unnötige Zuweisungen vermeidet. Es wäre interessant, die Top-Heap-Nutzung zu sehen, wenn Sie mit einigen größeren Sammlungen arbeiten, um zu sehen, wie sie miteinander verglichen werden.
Per Lundberg
12

Sie können das flatCollect()Muster aus Eclipse-Sammlungen verwenden .

MutableList<List<Object>> list = Lists.mutable.empty();
MutableList<Object> flat = list.flatCollect(each -> each);

Wenn Sie die Liste nicht ändern können von List:

List<List<Object>> list = new ArrayList<>();
List<Object> flat = ListAdapter.adapt(list).flatCollect(each -> each);

Hinweis: Ich bin ein Mitarbeiter von Eclipse Collections.

Nikhil Nanivadekar
quelle
21
Warum sollte eine Abhängigkeit von Drittanbietern verwendet werden, wenn die Funktionalität von Java 8 bereitgestellt wird?
saw303
2
Die Eclipse Collections-API befindet sich in der Sammlung selbst, daher ist der Code präzise. Dies ist einer der Hauptgründe in diesem Fall.
Nikhil Nanivadekar
11

Genau wie @Saravana erwähnt hat:

Flatmap ist besser, aber es gibt andere Möglichkeiten, dasselbe zu erreichen

 listStream.reduce(new ArrayList<>(), (l1, l2) -> {
        l1.addAll(l2);
        return l1;
 });

Zusammenfassend gibt es verschiedene Möglichkeiten, um dasselbe zu erreichen:

private <T> List<T> mergeOne(Stream<List<T>> listStream) {
    return listStream.flatMap(List::stream).collect(toList());
}

private <T> List<T> mergeTwo(Stream<List<T>> listStream) {
    List<T> result = new ArrayList<>();
    listStream.forEach(result::addAll);
    return result;
}

private <T> List<T> mergeThree(Stream<List<T>> listStream) {
    return listStream.reduce(new ArrayList<>(), (l1, l2) -> {
        l1.addAll(l2);
        return l1;
    });
}

private <T> List<T> mergeFour(Stream<List<T>> listStream) {
    return listStream.reduce((l1, l2) -> {
        List<T> l = new ArrayList<>(l1);
        l.addAll(l2);
        return l;
    }).orElse(new ArrayList<>());
}

private <T> List<T> mergeFive(Stream<List<T>> listStream) {
    return listStream.collect(ArrayList::new, List::addAll, List::addAll);
}
Gehört
quelle
11

Ich möchte nur ein weiteres Szenario wie erklären List<Documents>, diese Liste enthält einige weitere Listen anderer Dokumente wie List<Excel>, List<Word>, List<PowerPoint>. Die Struktur ist also

class A {
  List<Documents> documentList;
}

class Documents {
  List<Excel> excels;
  List<Word> words;
  List<PowerPoint> ppt;
}

Wenn Sie Excel nur aus Dokumenten iterieren möchten, gehen Sie wie folgt vor.

Der Code wäre also

 List<Documents> documentList = new A().getDocumentList();

 //check documentList as not null

 Optional<Excel> excelOptional = documentList.stream()
                         .map(doc -> doc.getExcel())
                         .flatMap(List::stream).findFirst();
 if(excelOptional.isPresent()){
   Excel exl = optionalExcel.get();
   // now get the value what you want.
 }

Ich hoffe, dies kann jemandes Problem beim Codieren lösen ...

Kushwaha
quelle
1

Wir können hierfür Flatmap verwenden, siehe folgenden Code:

 List<Integer> i1= Arrays.asList(1, 2, 3, 4);
 List<Integer> i2= Arrays.asList(5, 6, 7, 8);

 List<List<Integer>> ii= Arrays.asList(i1, i2);
 System.out.println("List<List<Integer>>"+ii);
 List<Integer> flat=ii.stream().flatMap(l-> l.stream()).collect(Collectors.toList());
 System.out.println("Flattened to List<Integer>"+flat);
Pratik Pawar
quelle
1

Eine Erweiterung von Erans Antwort, die die beste Antwort war. Wenn Sie mehrere Listenebenen haben, können Sie diese weiterhin flach abbilden.

Dies bietet auch eine praktische Möglichkeit zum Filtern, wenn Sie bei Bedarf auch die Ebenen herunterfahren.

Also zum Beispiel:

List<List<List<List<List<List<Object>>>>>> multiLayeredList = ...

List<Object> objectList = multiLayeredList
    .stream()
    .flatmap(someList1 -> someList1
        .stream()
        .filter(...Optional...))
    .flatmap(someList2 -> someList2
        .stream()
        .filter(...Optional...))
    .flatmap(someList3 -> someList3
        .stream()
        .filter(...Optional...))
    ...
    .collect(Collectors.toList())

Dies ähnelt in SQL SELECT-Anweisungen in SELECT-Anweisungen.

cody.tv.weber
quelle
1

Methode zum Konvertieren von a List<List>in List:

listOfLists.stream().flatMap(List::stream).collect(Collectors.toList());

Siehe dieses Beispiel:

public class Example {

    public static void main(String[] args) {
        List<List<String>> listOfLists = Collections.singletonList(Arrays.asList("a", "b", "v"));
        List<String> list = listOfLists.stream().flatMap(List::stream).collect(Collectors.toList());

        System.out.println("listOfLists => " + listOfLists);
        System.out.println("list => " + list);
    }

}       

Es druckt:

listOfLists => [[a, b, c]]
list => [a, b, c]
Soudipta Dutta
quelle