Comment transformer une liste de listes en liste dans Java 8?


534

Si j'ai un List<List<Object>>, comment puis-je le transformer en un List<Object>qui contient tous les objets dans le même ordre d'itération en utilisant les fonctionnalités de Java 8?

Réponses:


952

Vous pouvez utiliser flatMappour aplatir les listes internes (après les avoir converties en flux) en un seul flux, puis collecter le résultat dans une liste:

List<List<Object>> list = ...
List<Object> flat = 
    list.stream()
        .flatMap(List::stream)
        .collect(Collectors.toList());

11
@arshajii true, mais pour une raison quelconque, je préfère l'expression lambda. Peut-être que je n'aime pas le look de :::)
Eran

25
Class::methodse sent un peu bizarre au début, mais il a l'avantage de déclarer le type d'objet à partir duquel vous effectuez le mappage. C'est quelque chose que vous perdez autrement dans les flux.
ArneHugo

2
Pourtant, vous voudrez peut-être avoir un conteneur d'une liste et dans ce cas, vous devrez peut-être utiliser le lambda (l-> l.myList.stream ()).
Myoch

2
Si vous avez besoin de faire un cast explicite (tableau de primitives à lister par exemple), les lambdas peuvent également être nécessaires.
Michael Fulton,

52

flatmap c'est mieux mais il y a d'autres façons de faire la même chose

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

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

37

La flatMapméthode sur Streampeut certainement aplatir ces listes pour vous, mais elle doit créer des Streamobjets pour l'élément, puis un Streampour le résultat.

Vous n'avez pas besoin de tous ces Streamobjets. Voici le code simple et concis pour effectuer la tâche.

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

Parce que a Listest Iterable, ce code appelle la forEachméthode (fonctionnalité Java 8), héritée de Iterable.

Exécute l'action donnée pour chaque élément du Iterablejusqu'à ce que tous les éléments aient été traités ou que l'action lève une exception. Les actions sont exécutées dans l'ordre d'itération, si cet ordre est spécifié.

Et un Lists » Iteratorarticles de retours dans un ordre séquentiel.

Pour le Consumer, ce code transmet une référence de méthode (fonctionnalité Java 8) à la méthode pré-Java 8 List.addAllpour ajouter séquentiellement les éléments de la liste interne.

Ajoute tous les éléments de la collection spécifiée à la fin de cette liste, dans l'ordre où ils sont renvoyés par l'itérateur de la collection spécifiée (opération facultative).


3
Bonne suggestion alternative qui évite certaines allocations inutiles. Il serait intéressant de voir la meilleure utilisation du tas lorsque vous travaillez avec des collections plus importantes, pour voir comment elles se comparent.
Per Lundberg

12

Vous pouvez utiliser le flatCollect()modèle des collections Eclipse .

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

Si vous ne pouvez pas modifier la liste depuis List:

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

Remarque: je suis un contributeur aux collections Eclipse.


21
pourquoi utiliser une dépendance tierce lorsque la fonctionnalité est fournie par Java 8?
saw303

2
L'API Eclipse Collections se trouve sur la collection elle-même, donc le code est concis, est l'une des principales raisons dans ce cas.
Nikhil Nanivadekar

11

Tout comme @Saravana l'a mentionné:

flatmap est mieux, mais il existe d'autres façons d'obtenir le même

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

Pour résumer, il existe plusieurs façons d’obtenir les mêmes résultats comme suit:

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

Je veux juste expliquer un scénario plutôt List<Documents>, cette liste contient quelques listes de plus d'autres documents comme List<Excel>, List<Word>, List<PowerPoint>. La structure est donc

class A {
  List<Documents> documentList;
}

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

Maintenant, si vous souhaitez itérer Excel uniquement à partir de documents, faites quelque chose comme ci-dessous.

Donc, le code serait

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

J'espère que cela peut résoudre le problème de quelqu'un lors du codage ...


1

Nous pouvons utiliser une carte plate pour cela, veuillez vous référer au code ci-dessous:

 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

Une extension de la réponse d'Eran qui était la meilleure réponse, si vous avez un tas de couches de listes, vous pouvez continuer à les cartographier à plat.

Cela est également livré avec un moyen pratique de filtrer lorsque vous descendez les couches si nécessaire.

Ainsi, par exemple:

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

Cela serait similaire en SQL à avoir des instructions SELECT dans les instructions SELECT.


1

Méthode pour convertir un List<List>en List:

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

Voir cet exemple:

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

}       

Il imprime:

listOfLists => [[a, b, c]]
list => [a, b, c]
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.