5
\$\begingroup\$

I am trying to merge multiple map with Java stream API. The reqiurements like ...

  • this method can receive multiple map, does not care hot many of it
  • support 2 merge method: intersection, union
  • if key duplicates, it overwrites with later value

Here is basic code I wrote,

public class MapMergeHelper {
    public static Map<String, String> merge (MapMergeOperation mergeOperation, Map<String, String> ... maps) {
        Map<String, String> merged;
        switch (mergeOperation) {
            case INTERSECTION -> {
                Set<Map.Entry<String, String>> entrySet = maps[0].entrySet();
                for (int i = 1; i < maps.length; i++) {
                    final int current = i;
                    entrySet = entrySet.stream().filter(entry -> maps[current].containsKey(entry.getKey()))
                            .collect(Collectors.toSet());
                }
                merged = entrySet.stream()
                        .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue()));
            }
            default -> {
                // UNION -> If key duplicates, then override
                merged = Stream.of(maps)
                        .flatMap(entry -> entry.entrySet().stream())
                        .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue(), (o, o2) -> o2));
            }
        }
        return merged;
    }
}

MapMergeOperation is simple Enum like this

public enum MapMergeOperation {
    UNION,
    INTERSECTION
}

And here is the testcase

public class MapMergeHelperTest {

    @Test
    public void testMerge() {
        Map<String, String> testEntryMapA = Map.of("key1", "a", "key2", "b", "key3", "c", "key4", "d");
        Map<String, String> testEntryMapB = Map.of("key1", "b");
        Map<String, String> testEntryMapC = Map.of("key1", "a", "key5", "e");
        {
            // test intersection
            Map<String, String> actual = MapMergeHelper.merge(MapMergeOperation.INTERSECTION, testEntryMapA, testEntryMapB, testEntryMapC);
            assertThat(actual.keySet()).containsExactly("key1");
            assertThat(actual.get("key1")).isEqualTo("a"); // override
        }

        {
            // test union
            Map<String, String> actual = MapMergeHelper.merge(MapMergeOperation.UNION, testEntryMapA, testEntryMapB, testEntryMapC);
            assertThat(actual.keySet()).containsExactlyInAnyOrder("key1", "key2", "key3", "key4", "key5");
        }

    }
}

The problem is, I think it is not efficient enough because calling collect(Collections.toSet()) every time inside of for loop.

I am looking for a way to achieve goal in better readable, efficient way!

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Regarding the intersection, there is no need for streams as you can just do map[0].keySet().retainAll(map[i].keySet()); Loop in reverse order to retain the value in last map. \$\endgroup\$ Feb 9 at 10:17

1 Answer 1

7
\$\begingroup\$

Some basic tips:

  • Split this into 2 methods, drop the enum. It doesn't do you any good unless something is really forcing you to have it there. It will be a lot more readable and easier to have 2 separate functions intersect and union. Then you can worry about them separately, make separate tests for each of those as individual parts.
  • Don't mix streams and for loops. Choose one and stick with it. This makes the code confusing and less readable.
  • No point of this line final int current = i;, just use i directly or rename i to current.
  • Collecting to set is fine, but your logic could be improved. Take at look at Set.retainAll
  • Variable merged is unnecessary, you always assign it only to return in the next line. Just return the result already.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.