Reputation: 752
I need to refactor my below stream code:
List<Map<String, Integer>> list5 = new ArrayList<>();
map3 = new HashMap<>();
map3.put("foo", 1);
map3.put("bar", 2);
map3.put("zzz", 6);
list5.add(map3);
map3 = new HashMap<>();
map3.put("foo", 3);
map3.put("bar", 4);
map3.put("zzz", null);
list5.add(map3);
//new list with processed maps
List<Map<String, Integer>> list6 = list5.stream()
.map(hashmap -> {
Map<String, Integer> newMap = hashmap.entrySet().stream()
.collect(HashMap::new, (m, v) -> {
if (v.getKey().equals("bar")) {
m.put(v.getKey(), v.getValue() * 2);
} else {
m.put(v.getKey(), v.getValue());
}
}, HashMap::putAll);
return newMap;
})
.collect(toList());
System.out.println(list6);
I need a way to extract/refactor the below logic only from the above stream code, since this piece will only change in other list of maps that I have:
if (v.getKey().equals("bar")) {
m.put(v.getKey(), v.getValue() * 2);
} else {
m.put(v.getKey(), v.getValue());
}
Using IntelliJ it adds a biconsumer to main() itself, which is not expected here and removes the code. I need a way to extract it separately something like below:
List<Map<String, Integer>> list6 = list5.stream()
.map(hashmap -> {
Map<String, Integer> newMap = hashmap.entrySet().stream()
.collect(HashMap::new, (m, v) -> {
biconsumerLogic1.accept(m, v);
}, HashMap::putAll);
return newMap;
})
.collect(toList());
And biconsumerLogic1 is a separate functional interface like:
BiConsumer biconsumerLogic1() {
accept(m, v) {
//logic goes here...
}
}
How do I achieve that? Any pointers are appreciated.
Thanks..
Upvotes: 1
Views: 1206
Reputation: 752
Below is my version of the refactoring using BiConsumers:
psv main(...) {
List<Map<String, Integer>> newList = processMapData(origList, accumulator());
System.out.println(newList);
}
private static List<Map<String, Integer>> processMapData(List<Map<String, Integer>> origList, BiConsumer<HashMap<String, Integer>, Map.Entry<String, Integer>> accumulator) {
return origList.stream()
.map(hashmap -> {
Map<String, Integer> newMap = hashmap.entrySet().stream()
.collect(HashMap::new, accumulator, HashMap::putAll);
return newMap;
})
.collect(toList());
}
private static BiConsumer<HashMap<String, Integer>, Map.Entry<String, Integer>> accumulator() {
return (m, v) -> {
m.put(v.getKey(), v.getKey().equals("bar") ? v.getValue() * 2: v.getValue());
};
}
I could leverage more control and dynamically inject accumulators to processMapData
function.
Thanks to @Federico for the BiConsumer function suggestion.
Upvotes: 2
Reputation: 34470
I think there are better ways to do what you're actually doing. But strictly answering how to extract and refactor the indicated piece of code, you could do something like this:
static <K, V> List<Map<K, V>> process(
List<Map<K, V>> source,
BiConsumer<? super Map<K, V>, ? super Map.Entry<K, V>> accumulator) {
return source.stream()
.map(m -> m.entrySet().stream()
.collect(HashMap::new, accumulator, Map::putAll))
.collect(Collectors.toList());
}
Usage:
List<Map<String, Integer>> list6 = process(
list5,
(m, e) -> m.put(
e.getKey(),
e.getKey().equals("bar") ? e.getValue() * 2 : e.getValue()));
Upvotes: 2
Reputation: 3993
See, the problem is with using streams at all. You have overdesigned it, you don't need most of it.
Look:
List<Map<String, Integer>> newList = new ArrayList<>(list);
newList.replaceAll(eachMap -> {
Map<String, Integer> map = new HashMap<>(eachMap);
map.computeIfPresent("bar", (k,v) -> v * 2);
return map;
});
And to replace the action, you need to do something like this:
BiFunction<String, Integer, Integer> action = (k,v) -> v * 2;
newList.replaceAll(eachMap -> {
Map<String, Integer> map = new HashMap<>(eachMap);
map.computeIfPresent("bar", action);
return map;
});
Now you can extract the whole thing into a separate method and accept list
and action
(possibly the "bar"
variable as well) as arguments, and here you have your value replacer.
Upvotes: 3
Reputation: 425348
How about:
list5.stream().forEach(map -> map.replaceAll((k,v) -> k.equals("bar") ? v * 2 : v));
This does an in-place replacement of values.
If you absolutely need a new map:
list6 = list5.stream()
.map(m -> m.entrySet().stream()
.collect(toMap(Map.Entry::getKey, e -> e.getValue() * (e.getKey().equals("bar") ? 2 : 1))))
.collect(toList());
If you're looking to refactor, there are two options:
Create a BiFunction
, eg:
public static BiFunction<String, Integer, Integer> valueCalculator() {
return (k,v) -> k.equals("bar") ? v * 2 : v;
}
and to use:
list5.stream().forEach(map -> map.replaceAll(valueCalculator()));
Create a method:
public Integer valueCalculator(String key, Integer value) {
return key.equals("bar") ? value * 2 : value;
}
and to use, use a method reference:
list5.stream().forEach(map -> map.replaceAll(this::valueCalculator));
of if it's a static
method:
list5.stream().forEach(map -> map.replaceAll(MyClass::valueCalculator));
I prefer option 2, because it's easier understand, test and debug.
Upvotes: 1