Java Streams: Add a Map, but Avoid Mutation

I often find myself in a situation where I need to create Map objects from a set or List . The key is usually a String or Enum string or the like, and the value is some new object with merged data. The usual way to do this, for my part, is to first create a Map<String, SomeKeyValueObject> , and then iterate over the set or list that I enter and modify my newly created map.

As in the following example:

 class Example { Map<String, GroupedDataObject> groupData(final List<SomeData> list){ final Map<String, GroupedDataObject> map = new HashMap<>(); for(final SomeData data : list){ final String key = data.valueToGroupBy(); map.put(key, GroupedDataObject.of(map.get(key), data.displayName(), data.data())); } return map; } } class SomeData { private final String valueToGroupBy; private final Object data; private final String displayName; public SomeData(final String valueToGroupBy, final String displayName, final Object data) { this.valueToGroupBy = valueToGroupBy; this.data = data; this.displayName = displayName; } public String valueToGroupBy() { return valueToGroupBy; } public Object data() { return data; } public String displayName() { return displayName; } } class GroupedDataObject{ private final String key; private final List<Object> datas; private GroupedDataObject(final String key, final List<Object> list) { this.key = key; this.datas = list; } public static GroupedDataObject of(final GroupedDataObject groupedDataObject, final String key, final Object data) { final List<Object> list = new ArrayList<>(); if(groupedDataObject != null){ list.addAll(groupedDataObject.datas()); } list.add(data); return new GroupedDataObject(key, list); } public String key() { return key; } public List<Object> datas() { return datas; } } 

It is very unclean. We create a card, and then mutate it again and again.

I liked the use of java 8s Stream and the creation of non-mutating data structures (more precisely, you don't see the mutation). So, is there a way to turn this data grouping into something that uses a declarative approach rather than an imperative way?

I tried to implement a sentence in https://stackoverflow.com/a/167129/129 , but I seem to stumble. Using the approach in the answer (suggestion to use Collectors.groupingBy and Collectors.mapping ), I can get data sorted by map. But I can’t group the β€œdata” into the same object.

Is there a way to do this in a declarative way, or am I imperative?

+5
source share
3 answers

You can use Collectors.toMap with a merge function instead of Collectors.groupingBy .

 Map<String, GroupedDataObject> map = list.stream() .collect(Collectors.toMap(SomeData::valueToGroupBy, d -> { List<Object> l = new ArrayList<>(); l.add(d.data()); return new GroupedDataObject(d.valueToGroupBy(), l); }, (g1,g2) -> { g1.datas().addAll(g2.datas()); return g1; })); 

The GroupedDataObject constructor must be available for this to work.

+5
source

If you avoid GroupedDataObject and just want to get a map with a key and a list, you can use the Collectors.groupingBy that you studied.

Collectors.groupingBy will allow you to do this:

 List<SomeObject> list = getSomeList(); Map<SomeKey, List<SomeObject>> = list.stream().collect(Collectors.groupingBy(SomeObject::getKeyMethod)); 

This will require SomeKey to have the correct equals and hashValue

+1
source

Sometimes threads are not the way to go. I think this is one of those cases.

A little refactoring using merge() gives you:

 Map<String, MyTuple> groupData(final List<SomeData> list) { Map<String, MyTuple> map = new HashMap<>(); list.forEach(d -> map.merge(d.valueToGroupBy(), new MyTuple(data.displayName(), data.data()), (a, b) -> {a.addAll(b.getDatas()); return a;}); 

Assuming a reasonable class will contain your stuff:

 class MyTuple { String displayName; List<Object> datas = new ArrayList<>(); // getters plus constructor that takes 1 data and adds it to list } 
+1
source

Source: https://habr.com/ru/post/1273910/


All Articles