EnumMap is a Map implementation for Enumeration types. It extends AbstractMap and implements Map interface. EnumMap is a collection to store key value pairs similar to HashMap, however, the key in EnumMap, as you already might have guessed, is an Enum. Please go through https://delveintojava.blogspot.com/2019/11/hashmap-walkthrough.html for understanding HashMap concepts before starting this section.
EnumMap maintains it's entries in the natural order of their keys i.e. in the order in which enum constants are declared inside Enum. Let's clarify this with an example.
public enum Seasons {
WINTER, SPRING, SUMMER, FALL;
}
public static void main(String[] args) {
EnumMap<Seasons, String> funMap = new EnumMap<>(Seasons.class);
funMap.put(Seasons.WINTER, "Build a snowman");
funMap.put(Seasons.SPRING, "Maintain the garden");
funMap.put(Seasons.SUMMER, "Go surfing");
funMap.put(Seasons.FALL, "Lots of Shopping!");
System.out.println("EnumMap: " + funMap);
System.out.println("Key: " + Seasons.WINTER + " Value: " + funMap.get(Seasons.WINTER));
System.out.println("Does funMap has key " + Seasons.SPRING + ": " + funMap.containsKey(Seasons.SPRING));
System.out.println("Does funMap has value - Maintain the garden: " + funMap.containsValue("Maintain the garden"));
System.out.println("Does funMap has null value: " + funMap.containsValue(null));
}
OUTPUT:
EnumMap: {WINTER=Build a snowman, SPRING=Maintain the garden, SUMMER=Go surfing, FALL=Lots of Shopping!}
Key: WINTER Value: Build a snowman
Does funMap has key SPRING: true
Does funMap has value - Practice Quizes: true
Does funMap has null value: false
From the example, you can see that, we are creating an EnumMap object with key as Seasons which is an Enum. If we try to instantiate EnumMap with any class other than Enum, then it will result in a compilation error - Bound mismatch. As you can see, the class declaration below shows that EnumMap expects a key that is an Enum.
Let's check EnumMap's constructor, and understand why we give the class object as a parameter while instantiating EnumMap object.
In the constructor, first the key type is stored. Secondly, all the values of Enum type that we are using is returned by getKeyUniverse for caching purpose, since the number of keys in EnumMap is always constant. Finally, an array of object is created to store the values for each keys, and the array length is assigned the same value as the number of Enum key constants. In the above example, it would be 4.
PUT:
Let's look into the put implementation for EnumMap.
First the put implementation checks whether the key type is of the same class type as the one passed in the constructor. typeCheck method handles it. If the check fails, ClassCastException is thrown.
Next, the index to store values is calculated using the ordinal method provided by Enum. ordinal method returns the index of enum element. Ordinal of Enum elements start from 0. So, in our example, ordinal of WINTER = 0, SPRING = 1, SUMMER = 2, FALL = 3.
In the put implementation, we can see that maskNull is called to get the value to be stored. Why is maskNull required?
For that, let's understand one more property of EnumMap. It allows storage of null values, but not null keys as we know keys must be Enum constants (You will get NullPointerException if you try to insert a null key).get method (which we will see next) returns null when there is nothing stored for the Enum Constant key. It also returns null if we store a null value for that key. So, how do we know whether null is returned because we stored null, or because there is no value stored for the key. So, to handle this situation, maskNull returns a dummy object, and this object is stored in the EnumMap whenever user inserts null for any key (We will clarify it more later).
The value then is stored in the vals object array and the old value that we had retrieved earlier is returned using unmaskNull method.
GET:
get method calculates the index of the value with the key that is passed using the same ordinal method as put implementation, and returns the value using unmaskNull.
isValidKey checks whether the key is null or not, and also whether the key is of the Enum type that we saved while instantiating EnumMap. If not, the get method directly returns null.
So, the get method can return null either when the value is null or if there is no value or even if we pass null key. We can always check whether the null value actually exists by using containsValue and containsKey methods or containsMapping method.
The put and get methods simply use an array to store only values, and the implementation doesn't use complex data structure and logic to store values. This makes EnumMap highly efficient than HashMap. When you have a fixed number of keys to be used for storing values, it would be probably a good idea to use EnumMap.
EnumMap is not synchronized and is not suitable for multi threaded programs. It uses Fail Safe iterators as opposed to Fail fast iterator in HashMap.
References:
http://androidxref.com/9.0.0_r3/xref/libcore/ojluni/src/main/java/java/util/EnumMap.java
http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/EnumMap.java
EnumMap maintains it's entries in the natural order of their keys i.e. in the order in which enum constants are declared inside Enum. Let's clarify this with an example.
public enum Seasons {
WINTER, SPRING, SUMMER, FALL;
}
public static void main(String[] args) {
EnumMap<Seasons, String> funMap = new EnumMap<>(Seasons.class);
funMap.put(Seasons.WINTER, "Build a snowman");
funMap.put(Seasons.SPRING, "Maintain the garden");
funMap.put(Seasons.SUMMER, "Go surfing");
funMap.put(Seasons.FALL, "Lots of Shopping!");
System.out.println("EnumMap: " + funMap);
System.out.println("Key: " + Seasons.WINTER + " Value: " + funMap.get(Seasons.WINTER));
System.out.println("Does funMap has key " + Seasons.SPRING + ": " + funMap.containsKey(Seasons.SPRING));
System.out.println("Does funMap has value - Maintain the garden: " + funMap.containsValue("Maintain the garden"));
System.out.println("Does funMap has null value: " + funMap.containsValue(null));
}
OUTPUT:
EnumMap: {WINTER=Build a snowman, SPRING=Maintain the garden, SUMMER=Go surfing, FALL=Lots of Shopping!}
Key: WINTER Value: Build a snowman
Does funMap has key SPRING: true
Does funMap has value - Practice Quizes: true
Does funMap has null value: false
From the example, you can see that, we are creating an EnumMap object with key as Seasons which is an Enum. If we try to instantiate EnumMap with any class other than Enum, then it will result in a compilation error - Bound mismatch. As you can see, the class declaration below shows that EnumMap expects a key that is an Enum.
Let's check EnumMap's constructor, and understand why we give the class object as a parameter while instantiating EnumMap object.
In the constructor, first the key type is stored. Secondly, all the values of Enum type that we are using is returned by getKeyUniverse for caching purpose, since the number of keys in EnumMap is always constant. Finally, an array of object is created to store the values for each keys, and the array length is assigned the same value as the number of Enum key constants. In the above example, it would be 4.
PUT:
Let's look into the put implementation for EnumMap.
First the put implementation checks whether the key type is of the same class type as the one passed in the constructor. typeCheck method handles it. If the check fails, ClassCastException is thrown.
Next, the index to store values is calculated using the ordinal method provided by Enum. ordinal method returns the index of enum element. Ordinal of Enum elements start from 0. So, in our example, ordinal of WINTER = 0, SPRING = 1, SUMMER = 2, FALL = 3.
In the put implementation, we can see that maskNull is called to get the value to be stored. Why is maskNull required?
For that, let's understand one more property of EnumMap. It allows storage of null values, but not null keys as we know keys must be Enum constants (You will get NullPointerException if you try to insert a null key).get method (which we will see next) returns null when there is nothing stored for the Enum Constant key. It also returns null if we store a null value for that key. So, how do we know whether null is returned because we stored null, or because there is no value stored for the key. So, to handle this situation, maskNull returns a dummy object, and this object is stored in the EnumMap whenever user inserts null for any key (We will clarify it more later).
The value then is stored in the vals object array and the old value that we had retrieved earlier is returned using unmaskNull method.
GET:
get method calculates the index of the value with the key that is passed using the same ordinal method as put implementation, and returns the value using unmaskNull.
isValidKey checks whether the key is null or not, and also whether the key is of the Enum type that we saved while instantiating EnumMap. If not, the get method directly returns null.
So, the get method can return null either when the value is null or if there is no value or even if we pass null key. We can always check whether the null value actually exists by using containsValue and containsKey methods or containsMapping method.
The put and get methods simply use an array to store only values, and the implementation doesn't use complex data structure and logic to store values. This makes EnumMap highly efficient than HashMap. When you have a fixed number of keys to be used for storing values, it would be probably a good idea to use EnumMap.
EnumMap is not synchronized and is not suitable for multi threaded programs. It uses Fail Safe iterators as opposed to Fail fast iterator in HashMap.
References:
http://androidxref.com/9.0.0_r3/xref/libcore/ojluni/src/main/java/java/util/EnumMap.java
http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/EnumMap.java