Maps
Next, let’s explore another important collection, the map. Some programming languages, such as Python, refer to this collection as a dictionary as well.
A map is a collection that associates, or maps, a key to a value. To store something in a map, we must provide a unique key for each value. If we provide a key that is already in use, the value stored by that key is overwritten. Then, to retrieve a value, we simply provide the key that is associated with that value.
This is very similar to how arrays work, where each element in the array can be accessed using an array index. The biggest difference is that keys in a map can be any value, not just an integer, and likewise the values can be any type of value.
In Java, the collections framework provides an abstract class Map<K, V>
that defines the operations a list should be able to perform. So, as we saw in an earlier module, we can store our maps in the datatype Map
, but we cannot instantiate that class directly because it is abstract.
There are many classes in the Java collections framework that extend the Map
abstract class. The most commonly used version is the HashMap
and LinkedList
class.
Creating a Map
To use a map in our program, we’ll need to start by importing the appropriate libraries at the top of our file:
import java.util.Map;
import java.util.HashMap;
To create a map, we can simply instantiate it just like any other object. However, since the Java maps use generics, we must also provide the data types for both the keys and values inside of angle brackets <>
as well.
For example, to create a HashMap
that associates String
keys with Double
values, we would do the following:
Map<String, Double> aMap = new HashMap<String, Double>();
Notice that we have to use the Double
type instead of double
, because these maps can only store objects, not primitive data types, just like Lists
.
That’s all there is to it!
Map Operations
The Map
class in Java defines several operations that each map class must be able to perform. The full list can be found on the Map page of the Java API documentation. Here are a few of the most important ones:
put(k, v)
—associate the valuev
with the keyk
in the mapget(k)
—return the value associated with the keyk
in the mapsize()
—returns the number of key and value associations in the mapcontainsKey(k)
—returnstrue
if the map contains an associated value for the given keyk
containsValue(v)
—returnstrue
if one or more keys in the map are associated with the given valuev
Iterating
What if we want to iterate through a map? That is a bit tricky, but it can be done. To do this, we’ll use an enhanced for loop, which will use the special Map.Entry
class to represent each entry. The code for doing this is shown here:
Map<String, String> mapIter = new HashMap<String, String>();
mapIter.put("One", "Uno");
mapIter.put("Two", "Dos");
mapIter.put("Three", "Tres");
mapIter.put("Four", "Quatro");
for(Map.Entry<String, String> entry : mapIter.entrySet()){
System.out.println("Key: " + entry.getKey());
System.out.prinltn("Value: " + entry.getValue());
}
However, in most cases, it doesn’t make much sense to iterate through a map, since that isn’t what it is designed for. Instead, we may want to consider using some sort of a list instead.
Consider a map when:
- you want to use something other and integers as the index (key)
- you don’t care about the order the elements are yielded (provided) by iteration
Example
To explore how to use a map in a real program, let’s look at a quick example program. Here’s a short problem statement:
Write a program that will read multiple lines of input, either from the terminal or by reading a file provided as the first command-line argument. If no command-line argument is provided, assume that input should be read from the terminal. If there are any errors opening a file provided as an argument or parsing the input, simply print “Invalid Input!” and terminate the program.
The program will store a map that associates lines of input with random integers.
When a line of input is read, the program will check to see if that line has already been used as a key in the map. If so, it will return the value associated with that key.
If the map does not contain an entry for that key, the program should generate a new random integer and store it in the map, using the input line as the key.
The program should be implemented as two functions - a
main()
function that handles reading input, and agetOutput()
function that accepts a string as a parameter and returns the associated number for that string. It should also use a global variable to store the map.
For example, let’s say the program receives the following input:
cat
dog
horse
dog
cat
One possible output could be:
12334512345
239487234
234234
239487234
12334512345
Notice that the lines of output correspond with the lines of input, such that the program returns the same value each time it reads the word cat
.
main
Function
So, let’s build the program. We can start with this simple skeleton:
import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.ArrayIndexOutOfBoundsException;
import java.util.Map;
import java.util.HashMap;
import java.util.Random;
public class MapExample{
public static Map<String, Integer> map;
public static Random random;
public static void main(String[] args){
Scanner scanner;
try{
scanner = new Scanner(new File(args[0]));
}catch(FileNotFoundException e){
System.out.println("Invalid Input!");
return;
}catch(ArrayIndexOutOfBoundsException e){
//no argument provided, read from terminal
scanner = new Scanner(System.in);
}
try(
Scanner reader = scanner
){
while(reader.hasNext()){
String inp = reader.nextLine();
int output = getOutput(inp);
System.out.println(output);
}
}catch(Exception e){
System.out.println("Invalid Input!");
return;
}
}
public static int getOutput(String inp){
// MORE CODE GOES HERE
}
}
This program contains a simple main()
function that will handle reading and parsing the input from either the terminal or a file provided as a command-line argument. When it reads a line of input, it will use the getOutput()
function to get the associated output for that input, and then print it to the terminal.
The program also includes a global static field to store the map. We also do the same for a Random
object to generate random numbers. In that way, we can use the same objects in both functions without having to pass them around as arguments.
So, all we need to worry about implementing is the getOutput()
function.
getOutput
Function
Let’s dive into the getOutput()
function. First, we’ll need to see if the map
and random
objects have been initialized. We can do that by checking to see if they are equal to null
. If so, we’ll initialize them:
public static int getOutput(String inp){
if(map == null){
map = new HashMap<String, Integer>();
}
if(random == null){
random = new Random();
}
}
Then, we’ll need to see if the map contains a value for the string provided as input. If so, we can just return that value:
public static int getOutput(String inp){
if(map == null){
map = new HashMap<String, Integer>();
}
if(random == null){
random = new Random();
}
if(map.containsKey(inp)){
return map.get(inp);
}
}
However, if the map does not contain a value for that key, we must generate a new one, store it in the map, then return it:
public static int getOutput(String inp){
if(map == null){
map = new HashMap<String, Integer>();
}
if(random == null){
random = new Random();
}
if(map.containsKey(inp)){
return map.get(inp);
}else{
int newNumber = random.nextInt();
map.put(inp, newNumber);
return newNumber;
}
}
That’s all there is to it! See if you can complete the code in MapExample.java
to the left, then use the two assessments below to check your program.