Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grouping by object - Java streams

I have a list of data like the one given below:

List<Data> data = new ArrayList<Data>();
data.add(new Data("d1", "option1"));
data.add(new Data("d2", "option1"));
data.add(new Data("d1", "option2"));
data.add(new Data("d3", "option1"));
data.add(new Data("d3", "option2"));
data.add(new Data("d3", "option3"));

The structure looks like this:

class Data {
    private String name;
    private String option;
    private List<String> options = new ArrayList<>();
    public Data(String name, String option) {
        this.name = name;
        this.option = option;
    }

    public void addOption(String option) {
        options.add(option);
    }
}

How to group the items to a new array based on the name with its options,

[
"d1": {
    "name": "d1",
    "options": ["option1", "option2"]
},
"d2": {
    "name": "d2",
    "options": ["option1"]
},
"d3": {
    "name": "d3",
    "options": ["option1", "option2", "option3"]
}
]
like image 531
Vijay Veeraraghavan Avatar asked May 30 '18 06:05

Vijay Veeraraghavan


People also ask

What is group of object in Java?

The Collectors. groupingBy() method in Java 8 now permits developers to perform GROUP BY operation directly. GROUP BY is a SQL aggregate operation that is quite useful. It enables you to categorise records based on specified criteria.

How do you do Groupby in Java?

The groupingBy() method of Collectors class in Java are used for grouping objects by some property and storing results in a Map instance. In order to use it, we always need to specify a property by which the grouping would be performed. This method provides similar functionality to SQL's GROUP BY clause.

What is flatMap in Java stream?

Stream flatMap(Function mapper) returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element. Stream flatMap(Function mapper) is an intermediate operation. These operations are always lazy.

How do you stream an object in Java?

Just use Stream. of() to create a stream from a bunch of object references. Besides regular object streams Java 8 ships with special kinds of streams for working with the primitive data types int , long and double . As you might have guessed it's IntStream , LongStream and DoubleStream .


3 Answers

You can use a Collectors.toMap collector:

Map<String,Data>
    grouped = data.stream()
                  .collect(Collectors.toMap(Data::getName,
                                            d -> new Data(d.getName(),d.getOption()),
                                            (d1,d2) -> {d1.addOptions(d2.getOptions()); return d1;});

This will require changing the Data constructor to add the passed option to the options List as well as adding an addOptions method that receives a list of options and adds all of them to the options List of the current instance.

like image 177
Eran Avatar answered Oct 23 '22 20:10

Eran


Try this out,

final Map<String, List<String>> optionsByName = dataList.stream().collect(
        Collectors.groupingBy(Data::getName, Collectors.mapping(Data::getOption, Collectors.toList())));

I would suggest you to use this map as a source to create the DTO you need, without cluttering your code to get the exact result you want.

like image 34
Ravindra Ranwala Avatar answered Oct 23 '22 18:10

Ravindra Ranwala


You can use a simple grouping collector on the stream:

Map<String, List<String>> optionMap = data.stream()
            .collect(Collectors.groupingBy(Data::getName, 
                     Collectors.mapping(Data::getOption, Collectors.toList())));

When tested with your test list, the above produces this output:

{d1=[option1, option2], d2=[option1], d3=[option1, option2, option3]}

The above seems like a better, type-safe alternative to your desired map. Additionally, it avoids unnecessary duplication.

Your final map can nonetheless be computed based on the above:

Map<String, Map<String, Object>> m = new HashMap<>();
optionMap.entrySet().forEach(entry -> {
    Map<String, Object> map = new HashMap<>();
    map.put("name", entry.getKey());
    map.put("options", entry.getValue());

    m.put(entry.getKey(), map);
});

The m map above looks like:

{d1={name=d1, options=[option1, option2]}, 
 d2={name=d2, options=[option1]}, 
 d3={name=d3, options=[option1, option2, option3]}}

This is what you need, but it seems that it contains duplicated data, and it's less type-safe than the simple map in the previous result.

like image 2
ernest_k Avatar answered Oct 23 '22 20:10

ernest_k