Como transformar uma lista de listas em uma lista no Java 8?

533

Se eu tiver um List<List<Object>>, como posso transformá-lo em um List<Object>que contenha todos os objetos na mesma ordem de iteração usando os recursos do Java 8?

Sarah Szabo
fonte

Respostas:

951

Você pode usar flatMappara achatar as listas internas (após convertê-las em Streams) em um único Stream e, em seguida, coletar o resultado em uma lista:

List<List<Object>> list = ...
List<Object> flat = 
    list.stream()
        .flatMap(List::stream)
        .collect(Collectors.toList());
Eran
fonte
11
@arshajii true, mas por algum motivo eu prefiro a expressão lambda. Talvez eu não gosto do olhar de :::)
Eran
25
Class::methodparece um pouco estranho no começo, mas tem o benefício de declarar de que tipo de objeto você está mapeando. Isso é algo que você perde nos fluxos.
ArneHugo
2
No entanto, você pode querer ter um contêiner de uma lista e, nesse caso, pode precisar usar o lambda (l-> l.myList.stream ()).
22916 Myoch
2
Se você precisar fazer a conversão explícita (matriz de primitivas para Listar por exemplo), também poderão ser necessárias lambdas.
Michael Fulton
52

flatmap é melhor, mas existem outras maneiras de conseguir o mesmo

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

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

O flatMapmétodo on Streamcertamente pode achatar essas listas para você, mas deve criar Streamobjetos para o elemento e, em seguida, umStream para o resultado.

Você não precisa de todos esses Streamobjetos. Aqui está o código simples e conciso para executar a tarefa.

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

Como a Listé Iterable, esse código chama o forEachmétodo (recurso Java 8), que é herdado de Iterable.

Executa a ação especificada para cada elemento do Iterableaté que todos os elementos tenham sido processados ​​ou a ação lança uma exceção. As ações são executadas na ordem da iteração, se essa ordem for especificada.

E um List's Iteratorretorna itens em ordem seqüencial.

Para Consumerisso, esse código passa uma referência de método (recurso Java 8) para o método pré-Java 8 List.addAllpara adicionar os elementos da lista interna sequencialmente.

Anexa todos os elementos da coleção especificada ao final desta lista, na ordem em que são retornados pelo iterador da coleção especificada (operação opcional).

rgettman
fonte
3
Boa sugestão alternativa que evita algumas alocações desnecessárias. Seria interessante ver o melhor uso de heap disso ao trabalhar com algumas coleções maiores, para ver como elas se comparam.
Per Lundberg
12

Você pode usar o flatCollect()padrão nas Coleções Eclipse .

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

Se você não pode alterar a lista de List:

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

Nota: Sou colaborador do Eclipse Collections.

Nikhil Nanivadekar
fonte
21
por que usar uma dependência de terceiros quando a funcionalidade é fornecida pelo Java 8?
saw303
2
A API de coleções do Eclipse está na própria coleção, portanto, o código é conciso, é um dos principais motivos nesse caso.
Nikhil Nanivadekar
11

Assim como @Saravana mencionou:

O flatmap é melhor, mas existem outras maneiras de obter o mesmo

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

Em resumo, existem várias maneiras de obter o mesmo da seguinte maneira:

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

Eu só quero explicar mais um cenário como List<Documents>, esta lista contém mais algumas listas de outros documentos, como List<Excel>, List<Word>, List<PowerPoint>. Então a estrutura é

class A {
  List<Documents> documentList;
}

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

Agora, se você deseja iterar o Excel apenas a partir de documentos, faça algo como abaixo.

Então o código seria

 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 isso possa resolver o problema de alguém durante a codificação ...

Kushwaha
fonte
1

Podemos usar o flatmap para isso, consulte o código abaixo:

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

Uma expansão na resposta de Eran, que foi a principal, se você tiver várias camadas de listas, poderá continuar mapeando-as.

Isso também vem com uma maneira prática de filtrar conforme você desce as camadas, se necessário também.

Então, por exemplo:

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

Isso seria semelhante no SQL a ter instruções SELECT nas instruções SELECT.

cody.tv.weber
fonte
1

Método para converter um List<List>para List:

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

Veja este exemplo:

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
fonte