Java Basics: Maps

Demonstrating Basic Map Usage in Java

As a follow up to the last Java Basics post about arrays and collections, I’m going to talk about Maps.  A map is an Object that maps keys to values.  The Map interface and it’s implementations are part of the Java Collections Framework in spite of not being considered a true collection, because they can be manipulated like collections.

Posts in the series

  1. Java Basics: Control Flow Statements
  2. Java Basics: Exception Handling
  3. Java Basics: Custom Exceptions
  4. Java Basics: Arrays and Collections
  5. Java Basics: Maps (current post)
  6. Java Basics: File I/O
  7. Java Basics: NIO Files

Map Operations on HashMap

To demonstrate the usage of basic map operations, we’re going to exercise a HashMap.  It tends to be my go-to implementation when I just need a map and don’t need any special ordering or other features.

The default constructor sets an initial capacity of 16 with a load factor of .75.  According to the Javadoc, this is a happy medium in most cases between time and space considerations.  The load factor means that at initial capacity *  load factor the size of the map is doubled.  So in the default case 16 * .75 = 12.  When the twelfth key is stored, the map is doubled to a capacity of 32.

In this code, we’ll create a map with that takes Integer keys and stores String values.  We check right away to see if the map is empty using the isEmpty method and also display its size using the size method.  Then we add a few values and display the size again.

Map<Integer, String> exampleMap = new HashMap<Integer, String>();
System.out.println(String.format("Is the map empty? %b", exampleMap.isEmpty()));
System.out.println(String.format("Starting Map Size: %d", exampleMap.size()));
exampleMap.put(new Integer(1), "One");
exampleMap.put(new Integer(5), "Five");
exampleMap.put(new Integer(6), "Six");
exampleMap.put(new Integer(11), "eleven");
exampleMap.put(new Integer(20), "Twenty");
System.out.println(String.format("Map Size: %d", exampleMap.size()));

We use the containsKey method to see if a key already exists and the containsValue to see if a value already exists in the map.  Both methods return a boolean value.

boolean containsTwenty = exampleMap.containsKey(new Integer(20));
System.out.println(String.format("Contains key '%d'? %b", new Integer(20), containsTwenty));
System.out.println();

boolean containsOne = exampleMap.containsValue("One");
System.out.println(String.format("Contains value '%s'? %b", "One", containsOne));
System.out.println();

To replace the value associated with a key, use the replace method.  The replace only happens if there’s already a value associated with that key.  The method returns the value being replaced or null if no value is mapped.

String eleven = exampleMap.get(new Integer(11));
System.out.println(String.format("Value '%s' found at key %d.", eleven, new Integer(11)));
exampleMap.replace(new Integer(11), "Eleven");
eleven = exampleMap.get(new Integer(11));
System.out.println(String.format("After replace, value '%s' found at key %d.", eleven, new Integer(11)));
System.out.println();

To add a key/value pair only if the key is not already in the map, use putIfAbsent.  In the example, the seven key/value pair will be added, but the five will not because there’s already a key 5 in the map.

exampleMap.putIfAbsent(new Integer(7), "Seven");
System.out.println(String.format("After 'putIfAbsent' key %d, Map Size: %d", new Integer(7), exampleMap.size()));
exampleMap.putIfAbsent(new Integer(5), "five");
System.out.println(String.format("After 'putIfAbsent' key %d, Map Size: %d", new Integer(5), exampleMap.size()));
System.out.println();

As with the other collections, there is more than one way to iterate over the contents.

One way is to iterate over the keySet.  I tend to use for loops, but we could do this with a while loop as well.  As we iterate over each key, we’ll get the key from the iterator and can use it to retrieve the value using get.

System.out.println("--Iterating using the Key Set");
for (Iterator<Integer> it = exampleMap.keySet().iterator(); it.hasNext();) {
   Integer key = it.next();
   String value = exampleMap.get(key);
   System.out.println(String.format(" Key/Value Pair: %d = '%s'", key, value));
}
System.out.println();

Similarly,  we can use the entrySet method to iterate over the entries in the map.  The entrySet gives us map entries that consist of a key and a value that we can access using getKey and getValue.

System.out.println("--Iterating using Map.Entry");
for (Iterator<Map.Entry<Integer, String>> it = exampleMap.entrySet().iterator(); it.hasNext();) {
   Map.Entry<Integer, String> entry = it.next();
   System.out.println(String.format(" Key/Value Pair: %d = '%s'", entry.getKey(), entry.getValue()));
}
System.out.println();

If using Java 8 or above, we can use the forEach method with a lambda.

System.out.println("--Iterating using forEach with Lambdas");
exampleMap.forEach((key, value) -> System.out.println(String.format(" Key/Value Pair: %d = '%s'", key, value)));
System.out.println();

If we’re only interested in the values, we can simply iterate over the values.

System.out.println("--Iterating the values");
for (Iterator<String> it = exampleMap.values().iterator(); it.hasNext();) {
   String value = it.next();
   System.out.println(String.format(" Value: %s", value));
}
System.out.println();

Using TreeMap

TreeMap is an implementation of NavigableMap.  The map is sorted either by the natural ordering of its keys or using a Comparator if one is provided to the constructor.

In addition to the functionality that we exercised using a HashMap, the TreeMap has additional methods for iterating the map and retrieving values that are possible because we can count on the order being consistent.  Our example will demonstrate just one of these.  This chart illustrates the general differences between the methods for getting a single item.  There are also a whole bunch of methods for getting partial maps from a TreeMap.  I’ll let you explore those on your own.

JB_TreeMap_retrieval_method_chart

In this example, we set up a TreeMap with similar contents as the HashMap, but we can now expect the map to be ordered properly.  The first half of this method should look very familiar.  We create a map and add values to it.  We ask if it’s empty and check it’s size.  We also use the keySet method of iterating the contents.  Because of the consistent order, we can also get a descendingKeySet and iterate the map backward.  Lastly, we use the ceilingEntry method.  This method gets the least key that’s greater than or equal to the provided key.  So in the first usage, we get 5/Five back because there is an entry with key 5.  In the second usage, we also get 5/Five back because it’s greater than the 4 provided but less than the other values.

TreeMap<Integer, String> exampleMap = new TreeMap<Integer, String>();
System.out.println(String.format("Is the map empty? %b", exampleMap.isEmpty()));
System.out.println(String.format("Starting Map Size: %d", exampleMap.size()));
exampleMap.put(new Integer(1), "One");
exampleMap.put(new Integer(5), "Five");
exampleMap.put(new Integer(6), "Six");
exampleMap.put(new Integer(11), "eleven");
exampleMap.put(new Integer(20), "Twenty");
System.out.println(String.format("Map Size: %d", exampleMap.size()));
System.out.println();

System.out.println("--Iterating using the Key Set");
for (Iterator<Integer> it = exampleMap.keySet().iterator(); it.hasNext();) {
   Integer key = it.next();
   String value = exampleMap.get(key);
   System.out.println(String.format(" Key/Value Pair: %d = '%s'", key, value));
}
System.out.println();

System.out.println("--Iterating in reverse order using the Descending Key Set");
for (Iterator<Integer> it = exampleMap.descendingKeySet().iterator(); it.hasNext();) {
   Integer key = it.next();
   String value = exampleMap.get(key);
   System.out.println(String.format(" Key/Value Pair: %d = '%s'", key, value));
}
System.out.println();

Map.Entry<Integer, String> ceiling5 = exampleMap.ceilingEntry(new Integer(5));
System.out.println(String.format("Ceiling entry for key %d: %d = %s", new Integer(5), ceiling5.getKey(), ceiling5.getValue()));

Map.Entry<Integer, String> ceiling4 = exampleMap.ceilingEntry(new Integer(4));
System.out.println(String.format("Ceiling entry for key %d: %d = %s", new Integer(4), ceiling4.getKey(), ceiling4.getValue()));

Now let’s say we want to store a map of Hex Codes to color names, but instead of sorting them in their natural order, we want to sort them by their blueness, then greenness and finally redness.   We’re going to use the TreeMap constructor that takes a Comparator.  Since this is a simple example, we’re just going to define the comparator in-line using an anonymous inner class.  Then we add some random colors to it and then iterate it so we can see the sorting.

TreeMap<String, String> colorMap = new TreeMap<String, String>(new Comparator<String>() {

   @Override
   public int compare(String o1, String o2) {
      String lastTwo1 = o1.substring(o1.length() - 2);
      String lastTwo2 = o2.substring(o2.length() - 2);
      int compareLast2 = lastTwo1.compareToIgnoreCase(lastTwo2);
      if (compareLast2 == 0) {
         String middle2o1 = o1.substring(o1.length() - 4, o1.length() - 2);
         String middle2o2 = o2.substring(o2.length() - 4, o2.length() - 2);
         int compareMiddle2 = middle2o1.compareToIgnoreCase(middle2o2);
         if (compareMiddle2 == 0) {
            String first2o1 = o1.substring(o1.length() - 6, o1.length() - 4);
            String first2o2 = o2.substring(o2.length() - 6, o2.length() - 4);
            return first2o1.compareToIgnoreCase(first2o2);
         } else {
            return compareMiddle2;
         }
      } else {
         return compareLast2;
      }
   }
});

colorMap.putIfAbsent("#000000", "Black");
colorMap.putIfAbsent("000000", "Black");
colorMap.putIfAbsent("#FFFFFF", "White");
colorMap.putIfAbsent("#FF0000", "Red");
colorMap.putIfAbsent("#00FF00", "Lime");
colorMap.putIfAbsent("#0000FF", "Blue");
colorMap.putIfAbsent("#FFFF00", "Yellow");
colorMap.putIfAbsent("#00FFFF", "Cyan");
colorMap.putIfAbsent("#FF00FF", "Magenta");
colorMap.putIfAbsent("#C0C0C0", "Silver");
colorMap.putIfAbsent("#808080", "Gray");
colorMap.putIfAbsent("#00FFFF", "Cyan");
colorMap.putIfAbsent("#800000", "Maroon");
colorMap.putIfAbsent("#808000", "Olive");
colorMap.putIfAbsent("#008000", "Green");
colorMap.putIfAbsent("#800080", "Purple");
colorMap.putIfAbsent("#008080", "Teal");
colorMap.putIfAbsent("#000080", "Navy");
colorMap.putIfAbsent("#B22222", "Firebrick");
colorMap.putIfAbsent("#8FBC8F", "Dark Sea Green");
colorMap.putIfAbsent("#BA55D3", "Medium Orchid");
colorMap.putIfAbsent("#FFEFD5", "Papaya Whip");
colorMap.putIfAbsent("#E6E6FA", "Lavender");

System.out.println("--Iterating using the Key Set");
for (Iterator<String> it = colorMap.keySet().iterator(); it.hasNext();) {
   String key = it.next();
   String value = colorMap.get(key);
   System.out.println(String.format(" Key/Value Pair: %s = '%s'", key, value));
}
System.out.println();
}

This is the output of the map we just built.  The sorting is as we expected.  Also note, that because the comparator only deals with the last six characters, the map did not store the “000000”/”Black” entry.

Key/Value Pair: #000000 = 'Black'
Key/Value Pair: #800000 = 'Maroon'
Key/Value Pair: #FF0000 = 'Red'
Key/Value Pair: #008000 = 'Green'
Key/Value Pair: #808000 = 'Olive'
Key/Value Pair: #00FF00 = 'Lime'
Key/Value Pair: #FFFF00 = 'Yellow'
Key/Value Pair: #B22222 = 'Firebrick'
Key/Value Pair: #000080 = 'Navy'
Key/Value Pair: #800080 = 'Purple'
Key/Value Pair: #008080 = 'Teal'
Key/Value Pair: #808080 = 'Gray'
Key/Value Pair: #8FBC8F = 'Dark Sea Green'
Key/Value Pair: #C0C0C0 = 'Silver'
Key/Value Pair: #BA55D3 = 'Medium Orchid'
Key/Value Pair: #FFEFD5 = 'Papaya Whip'
Key/Value Pair: #E6E6FA = 'Lavender'
Key/Value Pair: #0000FF = 'Blue'
Key/Value Pair: #FF00FF = 'Magenta'
Key/Value Pair: #00FFFF = 'Cyan'
Key/Value Pair: #FFFFFF = 'White'

Conclusion

This covers the basics of using a map.  As with the other collections, the maps demonstrated here are not thread-safe, but there are concurrent-aware interfaces and implementations.  Additionally, there are third-party libraries, with Apache Commons Collections being a notable one.

References and further reading

4 thoughts on “Java Basics: Maps

Leave a comment