Gaurav Saini
Gaurav Saini

Reputation: 752

Streams Java 8 - Refactoring stream code

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

Answers (4)

Gaurav Saini
Gaurav Saini

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

fps
fps

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

M. Prokhorov
M. Prokhorov

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

Bohemian
Bohemian

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:

Option 1:

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()));

Option 2:

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

Related Questions