How can I perform two different functions in a collection based on grouping with a stream?

I am trying to refactor not very elegant code using a stream. I have a HashMap containing strings and MyObjects, and currently iterates through a for loop like this:

Map<String, MyObject> map = new HashMap<>(); Map<String, MyObject> objectsToAdd = new HashMap<>(); for(MyObject object : map.values()){ String idToAdd = object.getConnectedToId(); if(StringUtils.isEmpty(idToAdd) { continue; } if(idToAdd.substring(0,1).equals("i")){ // connected to an ICS MyObject newObject = service1.someMethod(idToAdd); if(newObject != null) { objectsToAdd.put(newObject.getId(), newObject); } } else if (idToAdd.substring(0,1).equals("d")){ // connected to a device MyObject newObject = service2.someMethod(idToAdd); if(newObject != null) { objectsToAdd.put(newObject.getId(), newObject); } } } map.putAll(objectsToAdd); 

Since I only care about identifiers, I started by using a map operation to get only identifiers, and then a filter operation to eliminate empty ones.

The next part is what I came across. The first thing I tried was to use the Collectors groupingBy command so that I could group the elements based on the first id character, and I ended up with this:

  map.values().stream() .map(myObject -> myObject.getConnectedToId()) // get a map of all the ids .filter(StringUtils::isNotEmpty) // filter non empty ones .collect( Collectors.mapping( MyObject::getId, Collectors.toList())), Collectors.groupingBy( s -> s.substring(0,1)); 

This link helped shorten using stream collectors: Stream Reduction

We have at least two problems with this code: 1) collect is an operation that closes the stream, and we have not finished yet, and 2) we still need the original object, but now it has been reduced to a map of connected ToIds.

Q1) Is there an intermediate operation that allows us to group objects based on the first id character?

Q2) How can we do this without reducing the collection to ids only?

Q3) And, finally, as soon as the collection is grouped (there will be two), how can we perform separate functions in each group, as in the source code?


Final Solution (thanks @Holger and @Flown for help)

  Map<Character, Function<String, MyObejct>> methodMapping = new HashMap<>(); methodMapping.put('i', service1::method1); methodMapping.put('d', service2::method2); Map<String, MyObject> toAdd = map.values().stream().map(MyObject::getConnectedToId) .filter(StringUtils::isNotEmpty) .map(id -> methodMapping.getOrDefault(id.charAt(0), i -> null).apply(id)) .filter(Objects::nonNull) .collect(Collectors.toMap(MyObject::getId, Function.identity(), (mo1, mo2) -> mo2)); map.putAll(toAdd); 

To avoid the exception of parallel modification, you must first save the objects in the temporary map when performing flow operations, and then after they are completed add them to the final map.

+5
source share
1 answer

Your Stream approach and your overall approach are very different in return types. So I converted your previous approach to the Stream API.

To reduce some of your code, you must first create a Map<Character, Function<String, MyObject>> to do a brief search in the matching step.
It looks something like this:

 Map<Character, Function<String, MyObject>> serviceMapping = new HashMap<>(); serviceMapping.put('i', service1); serviceMapping.put('d', service2); 

How does the pipeline work?

  • map MyObject β†’ MyObject::getConnectedToId
  • filter empty Strings
  • perform a search in serviceMap . If present, return Function<String, MyObject> , else id -> null
  • null filter
  • The last step is to collect the result by providing the necessary selection functions.

 Map<String, MyObject> toAdd = map.values().stream().map(MyObject::getConnectedToId) .filter(StringUtils::isEmpty) .map(id -> serviceMapping.getOrDefault(id.charAt(0), i -> null).apply(id)) .filter(Objects::nonNull) .collect(Collectors.toMap(MyObject::getId, Function.identity(), (mo1, mo2) -> mo2)); map.putAll(toAdd); 

You can also add calculated values ​​directly to map using the forEach operation.

 map.values().stream().map(MyObject::getConnectedToId) .filter(StringUtils::isEmpty) .map(id -> serviceMapping.getOrDefault(id.charAt(0), i -> null).apply(id)) .filter(Objects::nonNull) .forEach(mo -> map.put(mo.getId(), mo)); 
+3
source

All Articles