简体   繁体   中英

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

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 (ie 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?

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

    }
}

Edited to correct previous post based on clarification. Only difference between answer(s) by others is that the values() result is fed into an ArrayList constructor to create a List of Lists.

      // Create some data
      Random r = new Random(29);
      String codes = "ABCD";
      List<Widget> widgetList = r.ints(10, 0, 4).mapToObj(
            n -> new Widget(codes.substring(n, n + 1), "N" + i++)).collect(
                  Collectors.toList());

      // Now create the list of lists.

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

      // Display them
      for (List<?> list : listofWidgets) {
         System.out.println(list);
      }

Widget class


    class Widget {
       String widgetCode;
       String widgetName;

       public Widget(String wc, String wn) {
          widgetCode = wc;
          widgetName = wn;
       }

       public String getCode() {
          return widgetCode;
       }

       public String getName() {
          return widgetName;
       }

       public String toString() {
          return "{" + widgetCode + ":" + widgetName + "}";
       }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM