In the previous post we saw how EnumSet is an optimized collection built for use with Enums. While I genuinely struggled to come up with scenarios where I could need a set of enums, I have often come across and used Enums as keys in my maps. Java has come up with an optimized collection for the above use case too - EnumMap.
The maps that we use are hash based implementations. Adding a key value pair involves
These two features of an enum makes it possible to implement an array based fixed size map for it.
Consider the contains method for EnumMap:
The maps that we use are hash based implementations. Adding a key value pair involves
- computing a hash for the key,
- locating a bucket for the hash and
- then adding the key-value pair to the bucket.
These two features of an enum makes it possible to implement an array based fixed size map for it.
publicclass EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>As seen here, the EnumMap includes two fixed size arrays - an array of all possible Enum keys and an array of enum values.
implements java.io.Serializable, Cloneable
{
/**
* The <tt>Class</tt> object for the enum type of all the keys of this map.
*/
privatefinalClass<K> keyType;
/**
* All of the values comprising K. (Cached for performance.)
*/
privatetransient K[] keyUniverse;
/**
* Array representation of this map. The ith element is the value
* to which universe[i] is currently mapped, or null if it isn't
* mapped to anything, or NULL if it's mapped to null.
*/
privatetransientObject[] vals;
//remaining code...
}
Consider the contains method for EnumMap:
public boolean containsKey(Object key) {For any key, its ordinal acts as an index into the value array. If there is a value at this location it is returned, else null is returned. Thats it. No hashing, no buckets. This kind of implementation is what makes it both fast and compact.
return isValidKey(key) && vals[((Enum)key).ordinal()] != null;
}
public boolean containsValue(Object value) {The containsValue method simply runs a single for loop over the array looking for the passed object. In the hash based implementations, we have a double for loop - one for the buckets and the other for the entries inside the bucket to locate the value.
value = maskNull(value);
for (Object val : vals)
if (value.equals(val))
returntrue;
returnfalse;
}
public V get(Object key) {The get and put methods as seen simply access the location in our value array corresponding to ordinal associated with the passed key. Fast. And efficient.EnumMaps are something that we should use when working with enums as keys for our maps.
return (isValidKey(key) ?
unmaskNull(vals[((Enum)key).ordinal()]) : null);
}
public V put(K key, V value) {
typeCheck(key);
int index = ((Enum)key).ordinal();
Object oldValue = vals[index];
vals[index] = maskNull(value);
if (oldValue == null)
size++;
return unmaskNull(oldValue);
}
publicstatic void main(String[] args) {The output is :
EnumMap<Rating, String> enumMap = new EnumMap<Rating, String>(Rating.class);
System.out.println("is BAD key present ? " + enumMap.containsKey(Rating.BAD));
enumMap.put(Rating.BAD, Rating.BAD.name());
System.out.println("Is BAD key present ? " + enumMap.containsKey(Rating.BAD));
System.out.println("Is object for BAD key present ? " + enumMap.containsValue(Rating.BAD.name()));
System.out.println("Is object for BAD key present ? " + enumMap.get(Rating.BAD));
}
is BAD key present ? false
Is BAD key present ? true
Is object for BAD key present ? true
Is object for BAD key present ? BAD