Search code examples
javakeysetenum-map

Iterate over an EnumMap which doesn't lead to a new object creation per iteration


Is there a way to iterate over an EnumMap which doesn't lead to a new object creation per iteration? The entryset's iterator returns a new Entry each time. The only way I can see is

for(K k: map.keySet()) 
    foo(k, map.get(k));

To clarify this is specifically about EnumMap which has the following iterator implementation on its EntrySet

 public Map.Entry<K,V> next() {
        if (!hasNext())
            throw new NoSuchElementException();
        lastReturnedEntry = new Entry(index++);
        return lastReturnedEntry;
    }

Solution

  • First of all, from what you're saying it seems like you want the iterator to return a tuple of two objects.

    In Java the only way to do this is to wrap them in another object. (At the time of writing, that is.) So the iterator has to return an object other than the key and the value. That object has to be created at some point before a call to next() returns.

    With that constraint in mind there are three plausible paths to take:

    1. Create this entry object on put().
    2. Create the entry object on the first iteration across entrySet() (but cache it afterwards).
    3. Create a new entry object on every iteration of entrySet().

    The built-in EnumMap went for option 3 for the possible reasons that it is the simplest to implement and that it's the most economic solution if you don't need to iterate through entries. The drawback is that if you need to iterate more than once, you create more objects than any other solution.

    Option 1 is just as simple to implement, but there is an obvious overhead every time you add an entry to the map, even if you never intend to access it.

    Finally, option 2 involves slightly more code complexity, and a couple of edge cases when you alternate between iteration and adding more elements, but it gives you the best memory profile in theory.

    If the memory overhead of multiple iterations is proven to be a problem in your application, you can easily implement option 2, but I doubt the difference will be noticeable in most cases.

    P.s.: if you're willing to stray from idiomatic solutions and go into slightly insane territory, you can re-use the same Map.Entry instance for all your entries. This will obviously contradict what we expect from a Map.Entry, but it offers you the smallest memory allocation overhead and you can get away with it in simple iteration scenarios. Whether you end up with a faster end product is anybody's guess, you need to measure it.