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?

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

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

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


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.


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

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


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

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.


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]
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.