¿Cómo puedo convertir una lista de listas en una lista en Java 8?

534

Si tengo una List<List<Object>>, ¿cómo puedo convertirla en una List<Object>que contenga todos los objetos en el mismo orden de iteración utilizando las características de Java 8?

Sarah Szabo
fuente

Respuestas:

952

Puede usar flatMappara aplanar las listas internas (después de convertirlas en Streams) en una sola Stream y luego recopilar el resultado en una lista:

List<List<Object>> list = ...
List<Object> flat = 
    list.stream()
        .flatMap(List::stream)
        .collect(Collectors.toList());
Eran
fuente
11
@arshajii cierto, pero por alguna razón prefiero la expresión lambda. Quizás no me gusta el aspecto de :::)
Eran
25
Class::methodAl principio se siente un poco extraño, pero tiene la ventaja de que declara desde qué tipo de objeto está mapeando. Eso es algo que de lo contrario se pierde en las transmisiones.
ArneHugo
2
Sin embargo, es posible que desee tener un contenedor de una Lista y, en ese caso, es posible que deba usar lambda (l-> l.myList.stream ()).
Myoch
2
Si necesita hacer una conversión explícita (matriz de primitivas a la Lista, por ejemplo), entonces lambdas también puede ser necesario.
Michael Fulton
52

flatmap es mejor pero hay otras formas de lograr lo mismo

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

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

El flatMapmétodo Streamactivado puede aplanar esas listas por usted, pero debe crear Streamobjetos para el elemento y luego Streampara el resultado.

No necesitas todos esos Streamobjetos. Aquí está el código simple y conciso para realizar la tarea.

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

Como Listes Iterable, este código llama al forEachmétodo (característica Java 8), que se hereda de Iterable.

Realiza la acción dada para cada elemento del Iterablehasta que todos los elementos hayan sido procesados ​​o la acción arroje una excepción. Las acciones se realizan en el orden de iteración, si se especifica ese orden.

Y una List's Iteratordevuelve los artículos en orden secuencial.

Para el Consumer, este código pasa en una referencia de método (característica de Java 8) al método anterior a Java 8 List.addAllpara agregar los elementos de la lista interna secuencialmente.

Agrega todos los elementos de la colección especificada al final de esta lista, en el orden en que los devuelve el iterador de la colección especificada (operación opcional).

rgettman
fuente
3
Buena sugerencia alternativa que evita algunas asignaciones innecesarias. Sería interesante ver el uso principal de este montón cuando se trabaja con algunas colecciones más grandes, para ver cómo se comparan entre sí.
Por Lundberg
12

Puede usar el flatCollect()patrón de las colecciones de Eclipse .

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

Si no puede cambiar la lista de List:

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

Nota: Soy colaborador de Eclipse Collections.

Nikhil Nanivadekar
fuente
21
¿Por qué utilizar una dependencia de terceros cuando Java 8 proporciona la funcionalidad?
saw303
2
Eclipse Collections API está en la colección misma, por lo que el código es conciso, es una de las razones principales en este caso.
Nikhil Nanivadekar
11

Tal como mencionó @Saravana:

flatmap es mejor pero hay otras formas de lograr lo mismo

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

En resumen, hay varias formas de lograr lo mismo de la siguiente manera:

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
fuente
11

Sólo quiero explicar un escenario más bien List<Documents>, esta lista contiene unas cuantas listas de otros documentos como List<Excel>, List<Word>, List<PowerPoint>. Entonces la estructura es

class A {
  List<Documents> documentList;
}

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

Ahora, si desea iterar Excel solo a partir de documentos, haga lo siguiente:

Entonces el código sería

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

Espero que esto pueda resolver el problema de alguien mientras se codifica ...

Kushwaha
fuente
1

Podemos usar flatmap para esto, consulte el siguiente código:

 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
fuente
1

Una expansión en la respuesta de Eran que fue la respuesta principal, si tienes un montón de capas de listas, puedes seguir mapeándolas.

Esto también viene con una forma práctica de filtrado a medida que baja las capas si es necesario también.

Así por ejemplo:

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

Esto sería similar en SQL a tener instrucciones SELECT dentro de las instrucciones SELECT.

cody.tv.weber
fuente
1

Método para convertir un List<List>a List:

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

Ver este ejemplo:

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

}       

Imprime:

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