10

EnumMap<K extends Enum<K>, V> in Java is clearly ordered by definition of the associated enum, as you can also see in the javadoc:

Enum maps are maintained in the natural order of their keys (the order in which the enum constants are declared). This is reflected in the iterators returned by the collections views (keySet(), entrySet(), and values()).

What I need is a SortedMap using an enum as key type. I want to use methods like headMap() or firstKey(), but I want to profit from the added cpu+memory performance of EnumMaps. A TreeMap sounds like way too much overhead here.

Question: was this just missed in implementation, was it laziness (derived from AbstractMap) or is there a good reason why EnumMap is not a SortedMap?

rawcode
  • 105
  • 7
  • What about TreeSet? – Mohammed Deifallah Jan 15 '20 at 10:47
  • 1
    @MohammedDeifallah That is totally different, it has no keys... Did you mean `TreeMap`? – deHaar Jan 15 '20 at 10:49
  • 3
    I was able to find [this issue](https://bugs.openjdk.java.net/browse/JDK-6278287) for openjdk. It is from 2005 yet still open/unresolved. I'd assume there isn't any "good reason" for this being not implemented. – Amongalen Jan 15 '20 at 10:55
  • 1
    Thanks for the really quick answers, I have added that I find TreeMap too much overhead here, plus O(log n) for queries. But my question of course also counts for EnumSet and SortedSet, same problem. – rawcode Jan 15 '20 at 10:57
  • @Amongalen: your answer qualifies as answer to my question, even though it does not answer the root cause, besides "Oracle does not fogging care". Not even google found the OpenJDK issue you mentioned, so at least it will help others with the same problem. – rawcode Jan 15 '20 at 12:19

2 Answers2

4

Open feature request

I was able to find this issue for OpenJDK. It is from 2005 yet still open/unresolved.

I'd assume there isn't any "good reason" for this being not implemented.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Amongalen
  • 3,101
  • 14
  • 20
  • thank you for doing this. In the meantime I have seen ernest_k's answer which also does not answer my question actually, but brings up a nice solution for the problem underlying my question. I'm sorry I did not give you the credits as I mentioned first, but I think ernest_k deserves it for the work. – rawcode Jan 15 '20 at 13:18
  • @rawcode That is totally understandable. If I were you I would accept ernest_k's answer as well - it is way more helpful than mine. – Amongalen Jan 15 '20 at 13:48
3

This won't make an answer to your primary question (because only the original designers have the answer), but one approach I was considering was for you to implement it yourself. While trying to make a SortedMap implementation based on EnumMap, I came up with the following class.

This is surely a quick and dirty implementation (and note that it is not fully compliant with SortedMap - because the view requirements is not met), but if you need one, you can improve it:

class SortedEnumMap<K extends Enum<K>, V> 
    extends EnumMap<K, V> 
    implements SortedMap<K, V> {

    private Class<K> enumClass;
    private K[] values;

    public SortedEnumMap(Class<K> keyType) {
        super(keyType);
        this.values = keyType.getEnumConstants();
        this.enumClass = keyType;

        if (this.values.length == 0) {
            throw new IllegalArgumentException("Empty values");
        }
    }

    @Override
    public Comparator<? super K> comparator() {
        return Comparator.comparingInt(K::ordinal);
    }

    @Override
    public SortedMap<K, V> subMap(K fromKey, K toKey) {
        List<K> keys = Arrays.stream(this.values)
                .dropWhile(k -> k.ordinal() < fromKey.ordinal())
                .takeWhile(k -> k.ordinal() < toKey.ordinal())
                .collect(Collectors.toList());

        return this.forKeys(keys);
    }

    @Override
    public SortedMap<K, V> headMap(K toKey) {
        List<K> keys = new ArrayList<>();

        for (K k : this.values) {
            if (k.ordinal() < toKey.ordinal()) {
                keys.add(k);
            } else {
                break;
            }
        }

        return this.forKeys(keys);
    }

    @Override
    public SortedMap<K, V> tailMap(K fromKey) {
        List<K> keys = new ArrayList<>();

        for (K k : this.values) {
            if (k.ordinal() >= fromKey.ordinal()) {
                keys.add(k);
            }
        }

        return this.forKeys(keys);
    }

    //Returned map is NOT a "view" or the current one
    private SortedEnumMap<K, V> forKeys(List<K> keys) {
        SortedEnumMap<K, V> n = new SortedEnumMap<>(this.enumClass);
        keys.forEach(key -> n.put(key, super.get(key)));

        return n;
    }

    @Override
    public K firstKey() {
        return this.values[0];
    }

    @Override
    public K lastKey() {
        return this.values[this.values.length - 1];
    }
}

And for a quick test (bugs yet to be found):

SortedMap<Month, Integer> m = new SortedEnumMap(Month.class);

for (Month v : Month.values()) {
    m.put(v, v.getValue());
}

System.out.println("firstKey():       " + m.firstKey());
System.out.println("lastKey():        " + m.lastKey());
System.out.println("headMap/June:     " + m.headMap(Month.JUNE));
System.out.println("tailMap/June:     " + m.tailMap(Month.JUNE));
System.out.println("subMap/April-July " + m.subMap(Month.APRIL, Month.JULY));

I get:

firstKey():       JANUARY
lastKey():        DECEMBER
headMap/June:     {JANUARY=1, FEBRUARY=2, MARCH=3, APRIL=4, MAY=5}
tailMap/June:     {JUNE=6, JULY=7, AUGUST=8, SEPTEMBER=9, OCTOBER=10, NOVEMBER=11, DECEMBER=12}
subMap/April-July {APRIL=4, MAY=5, JUNE=6}
ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • 2
    You comment that "returned map is NOT a "view" or the current one", but these methods (head/sub/tail-Map) MUST return views. – assylias Jan 15 '20 at 13:08
  • 1
    I see that neither of your answers are _really_ an answer to my primary question, but your answer will be at least very helpful to other people with this problem, so I will give you the credits for your efforts. Maybe someone at Oracle will read and pull it... – rawcode Jan 15 '20 at 13:16
  • @assylias That's right, and I made explicit mention of it in the post – ernest_k Jan 15 '20 at 13:33
  • A sorted map that implements all those operations, intended to exploit the sorted nature, via linear searches… – Holger Jan 15 '20 at 19:30