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?

Sarah Szabo
la source

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());
Eran
la source
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);
Saravana
la source
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).

rgettman
la source
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.

Nikhil Nanivadekar
la source
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);
}
Hearen
la source
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 ...

Kushwaha
la source
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);
Pratik Pawar
la source
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.

cody.tv.weber
la source
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]
Soudipta Dutta
la source