Add support for Enum optimized collections
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:
- Fall back to JCF types, losing the benefits of Eclipse Collections' rich API
- Use general-purpose collections with unnecessary memory overhead and reduced performance
- Always use
Sets.adapt/Maps.adaptdepite the lack of immutable variants
Proposed Solution
Add enum-specific collection types following Eclipse Collections' mutable/immutable pattern:
Core types (high priority)
MutableEnumSet/ImmutableEnumSetMutableEnumMap/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
- Configuration management: Caching immutable enum-to-value mappings for application settings
- Feature flags: Storing enabled features as EnumSets with optimal performance
- State machines: Mapping enum states to handlers or validators
- Domain modeling: Representing enum-based relationships with proper type safety
- Grouping operations: Using
groupBywith 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.
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.