Zhongxin Yan created LANG-1795:
----------------------------------

             Summary: EnumUtils.getEnumMap throw IllegalStateException when 
keyFunction produces duplicate keys
                 Key: LANG-1795
                 URL: https://issues.apache.org/jira/browse/LANG-1795
             Project: Commons Lang
          Issue Type: Bug
          Components: lang.*
    Affects Versions: 3.20.0
            Reporter: Zhongxin Yan


EnumUtils.getEnumMap(...) currently collect enum values using 
Collectors.toMap(...) without a merge function.Because the default 
Collectors.toMap requires the key to be {*}unique{*}, these methods will throw 
an IllegalStateException if the provided keyFunction produces duplicate keys.

 
{code:java}
// code placeholder
Example:
class EnumUtilsDuplicateKeyTest {

    enum Color {
        RED, ROSE
    }
    @Test
    void testGetEnumMapThrowsOnDuplicateKey() {
        assertThrows(IllegalStateException.class, () -> {
            EnumUtils.getEnumMap(Color.class, c -> c.name().substring(0, 1));
        });
    }
} {code}
Why this happens
 * EnumUtils.getEnumMap(enumClass, keyFunction)constructs a Stream<E> of enum 
constants (via stream(enumClass)), and then collect(...)s them into a Map using 
Collectors.toMap.
 * The call pattern used is the two-argument Collectors.toMap(keyMapper, 
valueMapper)(i.e. Collectors.toMap(keyFunction::apply, Function.identity())). 
This toMap overload {*}requires keys to be unique{*}. Internally, on 
encountering a duplicate key it throws: “java.lang.IllegalStateException: 
Duplicate key <theKey>”
 * A caller-supplied keyFunction might easily produce duplicate keys — common 
examples:

 # keyFunction = e -> e.name().toLowerCase() — different-cased enum constants 
become identical keys.
 # keyFunction = e -> e.name().substring(0, 1) — multiple names starting with 
same letter collide.

There are multiple reasonable behaviors when keys collide (keep first, keep 
last, throw a clearer exception, or return a multi-valued map). The current 
implementation chooses an implicit and undocumented behavior (throw) by 
delegating to {{Collectors.toMap}} two-arg overload.

*Proposed fix*
Change the collection step to supply a merge function. A compatible, 
conservative default is to *preserve the first-seen value*

 



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to