Java 8: How to stream a list into a list of lists?

workerjoe :

Given a list of objects that need to be sorted and grouped:

static class Widget {
    // ...
    public String getCode() { return widgetCode; }
    public String getName() { return widgetName; }
}

List<Widget> widgetList = Arrays.asList(
    // several widgets with codes and names
);

I want to group the list into a list-of-lists, grouped by widgetCode, with the elements of each sub-list in the order they were encountered in the original list. I know that I can group them into a Map of lists using the groupingBy Collector:

Map<String,List<Widget>> widgetMap = widgetList.stream()
    .collect(groupingBy(Widget::getCode));

I do not take for granted that the keys are sorted, so I've taken the extra step of loading the whole thing into a SortedMap type:

SortedMap<String,List<Widget>> sortedWidgetMap = new TreeMap<String,List<Widget>>(
    widgetList.stream()
        .collect(groupingBy(Widget::getCode))
);

I know I can get a Collection from sortedWidgetMap by using .values(), and I guess it is an ordered collection because it comes from an ordered map type, so that's my current solution:

Collection<List<Widget>> widgetListList = new TreeMap<String,List<Widget>>(
    widgetList.stream()
        .collect(groupingBy(Widget::getCode))
).values();
widgetListList.forEach(System.out::println);  // do something with the data

This works so far, but I'm not confident that the resulting widgetListList is actually guaranteed to be in the right order (i.e. by widgetCode) or that the sub-lists will be built in the order they were found in the original list. Also, I think it must be possible to use the Stream API alone to achieve the output I want. So, how can I do this better?

Marco13 :

As mentioned in a comment, referring to a question that is very similar (in fact, I nearly considered it to be a duplicate...), the groupBy call comes in different flavors, and one of them allows passing in a factory for the map that is to be created.

So there is no need to explicitly wrap the result of the "simple" groupBy call into the creation of a new TreeMap, because you can create the TreeMap directly. This map (and its values() collection!) will be ordered by the key. The values of the map are lists, which are created using the downstream collector toList(), which explicitly says that it will collect the results in encounter order.

So the following should indeed be a simple, correct and efficient solution:

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class CollectToListOfList {
    static class Widget {
        String code;
        String name;

        Widget(String code, String name) {
            this.code = code;
            this.name = name;
        }

        public String getCode() {
            return code;
        }

        public String getName() {
            return name;
        }

        @Override
        public String toString() {
            return code + ": " + name;
        }

    }

    public static void main(String[] args) {
        List<Widget> widgetList = Arrays.asList(
            new Widget("0", "A"), 
            new Widget("1", "B"), 
            new Widget("2", "C"),
            new Widget("3", "D"), 
            new Widget("0", "E"), 
            new Widget("1", "F"), 
            new Widget("2", "G"),
            new Widget("3", "H"), 
            new Widget("0", "I"), 
            new Widget("1", "J"));

        Collection<List<Widget>> result = widgetList.stream()
            .collect(Collectors.groupingBy(Widget::getCode, TreeMap::new, Collectors.toList()))
            .values();

        for (List<Widget> list : result) {
            System.out.println(list);
        }

    }
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=133267&siteId=1