Vic
Vic

Reputation: 22041

Why would java map collector throw a duplicate key on the original object

When running the following code compiled on JDK 8 the exception thrown is

java.lang.IllegalStateException: Duplicate key 1

which indicates that even though the new key is supposed to be a different object from a different type, the exception still mentions the original object.

List<Integer> ints = ImmutableList.of(1, 1);
Map<String, Integer> m = ints.stream()
    .collect(Collectors.toMap(intgr -> String.valueOf(intgr + 1), Function.identity()));

The mapping function can be arbitrarily complex and totally different objects end up being mapped to the same key, why would this exception behavior be chosen?

I mean why the exception thrown is not "Duplicate key 2"?

Note: in our case the original value that was mapped is a third party class without toString implementation so it was impossible to know what caused the duplication.

Upvotes: 5

Views: 2270

Answers (2)

Tomasz Linkowski
Tomasz Linkowski

Reputation: 4496

This is because the merge function is called on values, and not on keys.

Excerpt from HashMap.merge function (JDK 1.8.0_152):

v = remappingFunction.apply(old.value, value);

And the merge function in the Collector.toMap is:

private static <T> BinaryOperator<T> throwingMerger() {
    return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
}

So I'd say it's a kind of inconsistency in the JDK. Instead of returning a message saying about "Duplicate key", the Collector should return a message about "Duplicate value".

EDIT: Not even an inconsistency but a JDK bug fixed in JDK 9, as pointed out in Andy Turner's and Jai's comment.

Upvotes: 1

Dean Xu
Dean Xu

Reputation: 4691

This could be a jdk bug. I think it may be resolved in higher version. (I'm using 1.8_162)

You can see Collectors#throwingMerger.

private static <T> BinaryOperator<T> throwingMerger() {
    return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
}

While the input parameter u, v is come from Map#merge. It is the old value and new value. So it is 1, 1 in your case. But the error message is Duplicate key, it's totally wrong because both u and v is not key but value.

EDIT

Checked jdk 10, this bug has been fixed.

See https://github.com/XDean/openjdk/blob/67672eec97164de10a9ca83ddbcef6b42816ed04/src/java.base/share/classes/java/util/stream/Collectors.java#L174

Now Collectors.toMap use its own accumulate function rather than use Map.merge.

Upvotes: 5

Related Questions