Get Map<String, List<Object>> from List<Object> where the key is one of the fields of the objects

Viewed 95

I've got an object as below

public class Model {
    private String key;
    private String data1;
    private String data2;
    private String data3;

    // getters
}

A list of these Models is being returned via the data layer. Now I want to create a Map<String, List<Model>> with the key being the "key" field in the Modelclass. There are multiple duplicate "key"s with different data values. I have the below existing solution but need a simpler and better way. Haven't used Java 8 extensively so not quite familiar with most of the methods and need a solution soon as I don't have time to research.

List<Model> models = modelRepo.getAllModels();
Set<String> keys = models.stream().map(Model::getKey).collect(Collectors.toSet());

Map<String, List<Model>> result = new HashMap<>();
keys.stream().forEach(key -> {
    List<Model> modelList = new ArrayList<>();
    models.stream().forEach(model -> {
        if (model.getKey().equals(key)) {
            modelList.add(model);
        }
    });
    result.put(key, modelList);
});
4 Answers

Use groupingBy for grouping Models by key which return Map

Map<String, List<Model>> results = models.stream()
            .collect(Collectors.groupingBy(Model::getKey));

groupingBy method which only takes a function as its parameter. The value that is returned by the function is used as a key to the map that we get from the groupingBy collector.

First of all, stream().forEach() can be replaced with forEach() as long as no intermediate operations are used.

What you want is a typical use-case for Collectors.groupingBy with no further downstream collector:

Map<String, List<Model>> result = models.stream()     // Stream<Model>
    .collect(Collectors.groupingBy(Model::getKey));   // Map<String, List<Model>>

You can use groupingBy:

public class Test1 {

    public static void main(String[] args) {
        List<Model> models = new ArrayList<>();
        models.add(new Model("a", "a1", "a2", "a3"));
        models.add(new Model("a", "a4", "a5", "a6"));
        models.add(new Model("c", "c1", "c2", "c3"));
        models.add(new Model("d", "d1", "d2", "d3"));
        models.add(new Model("d", "d4", "d5", "d6"));

        Map<String, List<Model>> result = models.stream().collect(Collectors.groupingBy(Model::getKey));

        result.forEach((a, b) -> System.out.println(a + " - " + b));
    }

    public static class Model {
        private String key;
        private String data1;
        private String data2;
        private String data3;

        public Model(String key, String data1, String data2, String data3) {
            this.key = key;
            this.data1 = data1;
            this.data2 = data2;
            this.data3 = data3;
        }

        public String getKey() {
            return key;
        }

        public void setKey(String key) {
            this.key = key;
        }

        public String getData1() {
            return data1;
        }

        public void setData1(String data1) {
            this.data1 = data1;
        }

        public String getData2() {
            return data2;
        }

        public void setData2(String data2) {
            this.data2 = data2;
        }

        public String getData3() {
            return data3;
        }

        public void setData3(String data3) {
            this.data3 = data3;
        }

        @Override
        public String toString() {
            return "Model{" +
                    "key='" + key + '\'' +
                    ", data1='" + data1 + '\'' +
                    ", data2='" + data2 + '\'' +
                    ", data3='" + data3 + '\'' +
                    '}';
        }
    }
}

Well for starters, the elegant way is that you can use the groupBy in stream().

Map<String, List<Model>> results = models.stream()
                                         .collect( Collectors.groupingBy(Model::getKey));

And if you are reluctant to use stream(), And also since you mention that you haven't used Java 8 extensively so not quite familiar with most of the methods. There's always the traditional way of doing this with loops.

    Map<String,List<Model>> results = new HashMap<>(  );

    for( Model m : models )
    {
        if( results.containsKey( m.getKey() ) )
        {
            results.get( m.getKey() ).add( m );
        }
        else
        {
            results.put( m.getKey(), new ArrayList<>( Collections.singleton(m) ) );
        }
    }
Related