Jak mogę zmienić listę list w listę w Javie 8?

532

Jeśli mam List<List<Object>>, w jaki sposób mogę zmienić to w List<Object>zawierające wszystkie obiekty w tej samej kolejności iteracji, używając funkcji Java 8?

Sarah Szabo
źródło

Odpowiedzi:

950

Możesz użyć, flatMapaby spłaszczyć wewnętrzne listy (po przekonwertowaniu ich na strumienie) w pojedynczy strumień, a następnie zebrać wynik w listę:

List<List<Object>> list = ...
List<Object> flat = 
    list.stream()
        .flatMap(List::stream)
        .collect(Collectors.toList());
Eran
źródło
11
@arshajii prawda, ale z jakiegoś powodu wolę wyrażenie lambda. Być może nie podoba mi się wygląd :::)
Eran
25
Class::methodna początku wydaje się trochę dziwny, ale ma tę zaletę, że deklaruje, z jakiego obiektu mapujesz. To jest coś, co inaczej tracisz w strumieniach.
ArneHugo
2
Jednak możesz chcieć mieć kontener z Listą i w takim przypadku może być konieczne użycie lambda (l-> l.myList.stream ()).
Myoch
2
Jeśli potrzebujesz wykonać jawne rzutowanie (na przykład tablica operacji podstawowych na List), może być konieczne także lambdas.
Michael Fulton
52

flatmap jest lepszy, ale istnieją inne sposoby osiągnięcia tego samego

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

List<Object> collect = 
      listOfList.stream()
                .collect(ArrayList::new, List::addAll, List::addAll);
Saravana
źródło
37

Ta flatMapmetoda Streammoże z pewnością spłaszczyć te listy, ale musi tworzyć Streamobiekty dla elementu, a następnie Streamdla wyniku.

Nie potrzebujesz wszystkich tych Streamobiektów. Oto prosty, zwięzły kod do wykonania zadania.

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

Ponieważ Listjest Iterableto kod wywołuje się forEachmetodę (Java 8 funkcji), który jest dziedziczony Iterable.

Wykonuje podaną akcję dla każdego elementu, Iterabledopóki wszystkie elementy nie zostaną przetworzone lub akcja wygeneruje wyjątek. Akcje są wykonywane w kolejności iteracji, jeśli ta kolejność jest określona.

A List„s Iteratorelementy powraca w kolejności.

W Consumertym przypadku kod przekazuje odwołanie do metody (funkcja Java 8) do metody wcześniejszej niż Java 8, List.addAllaby sekwencyjnie dodawać wewnętrzne elementy listy.

Dołącza wszystkie elementy w określonej kolekcji na końcu tej listy, w kolejności, w jakiej są zwracane przez iterator określonej kolekcji (operacja opcjonalna).

rgettman
źródło
3
Dobra alternatywa, która pozwala uniknąć niepotrzebnych przydziałów. Byłoby interesująco zobaczyć użycie tego na stosie przy pracy z niektórymi większymi kolekcjami, aby zobaczyć, jak się ze sobą porównują.
Per Lundberg,
12

Możesz użyć flatCollect()wzorca z kolekcji Eclipse .

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

Jeśli nie możesz zmienić listy z List:

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

Uwaga: Jestem współtwórcą kolekcji Eclipse.

Nikhil Nanivadekar
źródło
21
dlaczego warto korzystać z zależności stron trzecich, gdy funkcjonalność zapewnia Java 8?
saw303,
2
Interfejs API Eclipse Collections znajduje się w samej kolekcji, więc kod jest zwięzły, jest to jeden z głównych powodów tego przypadku.
Nikhil Nanivadekar
11

Tak jak wspomniano @ Saravana:

flatmap jest lepszy, ale istnieją inne sposoby osiągnięcia tego samego

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

Podsumowując, istnieje kilka sposobów osiągnięcia tego samego, co następuje:

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);
}
Hearen
źródło
11

Chcę tylko wyjaśnić jeszcze jeden scenariusz jak List<Documents>ta lista zawiera kilka dodatkowych wykazów innych dokumentów, takich jak List<Excel>, List<Word>, List<PowerPoint>. Więc struktura jest

class A {
  List<Documents> documentList;
}

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

Teraz, jeśli chcesz iterować Excela tylko z dokumentów, zrób coś takiego jak poniżej.

Tak więc kod będzie

 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.
 }

Mam nadzieję, że to rozwiąże czyjś problem podczas kodowania ...

Kushwaha
źródło
1

Możemy użyć do tego płaskiej mapy, zapoznaj się z poniższym kodem:

 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
źródło
1

Rozszerzenie odpowiedzi Erana, które było najlepszą odpowiedzią, jeśli masz kilka warstw list, możesz je nadal płasko odwzorowywać.

Jest to również przydatny sposób filtrowania podczas schodzenia po warstwach, jeśli to konieczne.

Na przykład:

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

W SQL byłoby to podobne do posiadania instrukcji SELECT w instrukcjach SELECT.

cody.tv.weber
źródło
1

Metoda konwersji List<List>na List:

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

Zobacz ten przykład:

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

}       

Drukuje:

listOfLists => [[a, b, c]]
list => [a, b, c]
Soudipta Dutta
źródło