eclipse-collections icon indicating copy to clipboard operation
eclipse-collections copied to clipboard

Add support for Enum optimized collections

Open tampix opened this issue 3 months ago • 1 comments

Add EnumSet and EnumMap implementations to Eclipse Collections

Problem Statement

Eclipse Collections currently lacks dedicated implementations for enum-based collections, while the JCF provides EnumSet and EnumMap as memory-efficient and performance-optimized containers for enums. This gap forces users to either:

  1. Fall back to JCF types, losing the benefits of Eclipse Collections' rich API
  2. Use general-purpose collections with unnecessary memory overhead and reduced performance
  3. Always use Sets.adapt / Maps.adapt depite the lack of immutable variants

Proposed Solution

Add enum-specific collection types following Eclipse Collections' mutable/immutable pattern:

Core types (high priority)

  • MutableEnumSet / ImmutableEnumSet
  • MutableEnumMap / ImmutableEnumMap

Extended types (medium to low priority)

  • MutableEnumBiMap / ImmutableEnumBiMap -> use cases are limited
  • Support for all Multimaps combinations -> very complex API wise

Benefits

For MutableEnumSet/MutableEnumMap:

  • Maintains API consistency with SetIterable/MapIterable
  • Provides performance benefits of JCF's enum-optimized implementations
  • Enables seamless integration with existing Eclipse Collections code

For ImmutableEnumSet/ImmutableEnumMap:

  • Allows caching of declarative configurations with immutability guarantees
  • Maintains API consistency across the library
  • Supports functional programming patterns common in Eclipse Collections usage

For MutableEnumBiMap/ImmutableEnumBiMap:

  • Completes the enum collection family for consistency
  • Utility is more limited but valuable for specific use cases

For enum-based multimaps:

  • Enables efficient enum-to-collection mappings
  • See notes below regarding API complexity considerations

Notes for Multimaps

Enum-based multimaps introduce significant API complexity due to combinatorial explosion of factory methods and type combinations:

// Different factory entry points depending on key/value types:
var objToEnums = Multimaps.mutable.enums.of("", AnEnum.KEY);
var enumToObj = EnumMultimaps.mutable.list.of(AnEnum.KEY, "");
var enumToEnums = EnumMultimaps.mutable.enums.of(AnEnum.KEY, AnotherEnum.STUFF);

// Usage can become verbose:
MutableEnumEnumMultimap<AnEnum, AnotherEnum> enumToEnums = EnumSets.mutable
    .allOf(AnotherEnum.class)
    .groupByEach(AnotherEnum::supportedAnEnumValues,
                 /*
                  * Note that the factory method `noneOf` would have to take the 2 classes,
                  * as both universes must be known during construction.
                  */
                 EnumMultimaps.mutable.enums.noneOf(AnEnum.class, 
                                                    AnotherEnum.class));

Despite this complexity, enum multimaps would still provide value for:

  • Grouping operations with enum keys
  • Efficient enum-to-enum relationship mappings
  • Maintaining API consistency with the existing multimap hierarchy

The verbosity is a trade-off for type safety and performance optimization. Consider whether simplified factory methods or builder patterns could mitigate the complexity.

Proposed API (sketch)

Factory methods

// Factory methods for core types
MutableEnumSet<MyEnum> set = EnumSets.mutable.of(MyEnum.VALUE1, MyEnum.VALUE2);
MutableEnumSet<MyEnum> set = EnumSets.mutable.complementOf(MyEnum.VALUE1, MyEnum.VALUE2);
MutableEnumSet<MyEnum> set = EnumSets.mutable.allOf(MyEnum.class);
MutableEnumSet<MyEnum> set = EnumSets.mutable.noneOf(MyEnum.class);
ImmutableEnumSet<MyEnum> immutable = EnumSets.immutable.of(MyEnum.VALUE1);

MutableEnumMap<MyEnum, String> empty = EnumMaps.mutable.noneOf(MyEnum.class);
MutableEnumMap<MyEnum, String> map = EnumMaps.mutable.of(MyEnum.KEY, "value");
ImmutableEnumMap<MyEnum, String> immutable = EnumMaps.immutable.of(MyEnum.KEY, "value");

// BiMap variants
MutableEnumBiMap<MyEnum, String> biMap = EnumBiMaps.mutable.of(MyEnum.KEY, "value");

// Multimap variants
MutableEnumSetMultimap<MyEnum, String> multimap = EnumMultimaps.mutable.set.of(MyEnum.KEY, "value");
MutableEnumListMultimap<MyEnum, String> multimap = EnumMultimaps.mutable.list.of(MyEnum.KEY, "value");

Usage

ImmutableEnumSet<MyEnum> complement = EnumSets.immutable.of(MyEnum.VALUE1).complement();
ImmutableEnumSet<MyEnum> without = complement.without(MyEnumVALUE2, MyEnum.VALUE3);
ImmutableEnumSet<MyEnum> with = without.with(MyEnum.VALUE1);

Use Cases

  1. Configuration management: Caching immutable enum-to-value mappings for application settings
  2. Feature flags: Storing enabled features as EnumSets with optimal performance
  3. State machines: Mapping enum states to handlers or validators
  4. Domain modeling: Representing enum-based relationships with proper type safety
  5. Grouping operations: Using groupBy with enum keys for efficient categorization

Additional Context

This feature would improve interoperability between Eclipse Collections and codebases that heavily use enums, which is common in enterprise Java applications. The performance characteristics of EnumSet and EnumMap (backed by bit vectors and arrays respectively) make them significantly faster than hash-based alternatives for enum keys.

tampix avatar Sep 30 '25 12:09 tampix

As a side note, I digged a bit into implementing that feature.

Both EnumSet and EnumMap cache the shared underlying array of the enum for both performance and memory reasons (no array copy needed compared to Enum.values / Class.getEnumConstants). So :

  • Easy solution : wrap the JCF implementation, but no way to implement union/difference and such in O(1) (just like the JCF)
  • Intermediate solution : reimplement it, but accept the cost of O(n) at each instantiation and the memory overhead per instance
  • Heavy but maybe acceptable solution : keep a map to cache previous calls to Class.getEnumConstants. This has some additional memory overhead, but might be the less hacky solution.
  • Complex solution : reimplement it with a proper unsafe cache like the JCF which... will be problematic with modules

If some people think this is an interesting feature to have, I'd be glad to work on it.

tampix avatar Oct 20 '25 10:10 tampix