These days, the idea a physical dictionary is pretty old-fashioned. Consulting a physical book to learn the meaning of a word or phrase makes no sense when a simple Google search will give you all the information you need.
But a dictionary is a very useful metaphor for an important programming concept: key-value pairs. Dictionaries operate by linking a word (a “key”) to its meaning (a “value”). This key-value pair is a fundamental data model in programming.
Most programming languages use hashes to ensure the uniqueness of keys and make storage and retrieval of values more efficient. A HashMap
in Java puts all of that together to allow programmers to store key-value pairs by hashing their keys upon insertion and retrieval.
In this tutorial, we’ll learn how to build a study guide program by storing Java concepts and their corresponding meanings in a HashMap to be retrieved when it’s time to study. The source code for this tutorial can be found here
Key Takeaways
- The tutorial explains how to build a study guide app using Java HashMap, a data structure that stores key-value pairs. The app stores Java concepts and their meanings as key-value pairs in a HashMap.
- The HashMap class in Java is a powerful tool for storing key-value pairs, ensuring the uniqueness of keys and making storage and retrieval of values more efficient. It encapsulates much of the heavy lifting, allowing programmers to focus on implementing the functionality they need.
- The tutorial uses a ‘Meaning’ class to model the meaning of Java concepts. This class tokenizes the meaning of a concept and checks if a user’s input matches the concept’s meaning, returning true if 65% of the meaning’s tokens match the user’s input.
- The ‘StudyGuide’ class is the main class where the study guide runs. It initializes a HashMap to hold the key-value pairs, populates the study guide with Java concepts, and starts a study session where users can guess the meanings of concepts.
- The tutorial also demonstrates how to use various HashMap methods such as put(), get(), and remove(), and explains how to handle situations such as updating possible points, checking for null values, and dealing with user input.
HashMap
Java maps make programs more efficient by storing key-value pairs in a single data structure. Alternatively, the same could be accomplished with two manually synced data structures — one holding keys and the other holding values — but this isn’t the best approach to mapping keys to values; that’s what a Map
is for. The HashMap
is arguably one of the most powerful of them all. All keys of the map must be of the same type, as must its values.
In the background, Java’s HashMap class stores the pairs in arrays, but we don’t have to worry much about the implementation because much of the heavy lifting is encapsulated.
The Map
interface provides methods for putting pairs in the map, replacing the value of pairs, removing pairs by key, checking for the existence of a pair by key, and clearing the map. We’ll make use of almost all of those functions today.
The Application
Our app will be a simple study guide app. We’ll build our HashMap
to store our concepts and use Java control-flow statements to loop until we have provided the correct meaning for each concept. Let’s build!
Classes
We’ll need two classes to run our study guide application. The first class will be our main class to run our program and the second will be another class to model the meaning of Java concepts and encapsulate a couple of fields and methods to simplify the study guide interface.
The first thing we need to do is create a new Java project with a main class. Once we have that set up, let’s go ahead and build our Meaning
class that will be used in our Main
.
Meaning Class
Create a new class called “Meaning”. We won’t be diving into this class, but let’s go over the importance of it. This class will model the meaning of Java concepts to be studied by holding their meaning and tokens of that meaning. Here are its important parts:
public class Meaning {
/*if a user's guess at the meaning matches at least 65% of the
actual meaning of a Meaning, the user is correct.*/
private static final double CORRECTNESS_THRESHOLD_PERCENTAGE = .65;
//the string representation of this meaning
private String meaning;
//whether or not the user's input matches this meaning
private boolean gotCorrect;
//holds the tokens of the meaning
private String[] meaningTokens;
//the constructor
public Meaning(String meaning) {
this.meaning = meaning;
tokenizeMeaning();
}
//omitted ...
/**
* This is a naive lexical analyzer
* that counts how many words match
* between the user input and this meaning.
*
* There are many ways to improve this.
* @param userInput the user's guess at what this concept means
* @return true if the user's input matches
* this meaning.
*/
public boolean userInputMatches(String userInput) {
//make sure the user's input is not empty
if(userInput != null && !userInput.isEmpty()) {
int numMatchedTokens = 0;
userInput = removeAllPunctuation(userInput);
String[] userInputTokens = userInput.split(" ");
//total tokens. The greater of the two
int totalTokens = Math.max(userInputTokens.length,
meaningTokens.length);
/*get the number of matched tokens
between the two sets of tokens.*/
numMatchedTokens = getNumMatchedTokens(userInputTokens);
//cast to a double to get floating point result
double matchPercentage = ((double)numMatchedTokens / totalTokens);
//return whether or not the matched percentage meets the threshold
return matchPercentage >= CORRECTNESS_THRESHOLD_PERCENTAGE;
}
//return false because the user's input is empty
return false;
}
//omitted ...
}
To construct a new Meaning
instance, we pass the meaning of a concept to the constructor. From there, the meaning will be tokenized and stored in an array for us to use when checking if the user’s input matches the meaning of the concept.
When the program runs, the user will be asked to input a guess at the meaning of a concept, and we’ll use that input to check if the user is correct by calling the userInputMatches()
instance method of Meaning
. This method will tokenize the user’s input and loop through the meaning’s tokens and the user’s input tokens to count their matches in order. We’ll return true if 65% of the meaning’s tokens are matched by the user’s input tokens; this means the user answer is correct and so they get a point.
That’s all we need Meaning
for, but you can view the full class in the supporting code for this tutorial.
Main Class
Our study guide will run in the main class. Feel free to call this class whatever you want, but I’ll call mine “StudyGuide”.
public class StudyGuide {
//omitted ...
//initialize a HashMap to hold String-Meaning (key-value) pairs.
private static final HashMap<String, Meaning> studyGuide = new HashMap<>();
/*The total possible points the user can obtain.
Equal to the number of concepts in the study guide.*/
private static int possiblePoints;
//our main method to start our app
public static void main(String[] args) {
populateStudyGuide();
printInstructions();
study();
}
//omitted ...
}
Our StudyGuide
class begins pretty humbly. We start by initializing a final static HashMap
called studyGuide
as our study guide. All the keys of studyGuide
, representing our concepts, will be of the String
type while the values, the meanings of our concepts, will be of our custom Meaning
type.
We begin our app by calling populateStudyGuide
to populate studyGuide
with concept-meaning pairs:
/**
* Populates the study guide with Java concepts.
*/
private static void populateStudyGuide() {
//add a bunch of
studyGuide.put("Variable", new Meaning(
"Used to store a single value for later use."));
studyGuide.put("String", new Meaning(
"A class for representing character strings."));
studyGuide.put("double", new Meaning(
"A primitive datatype for representing floating point numbers."));
studyGuide.put("Double", new Meaning(
"A class for wrapping a double in an Object with convenient methods."));
studyGuide.put("zero", new Meaning(
"The number zero. The first index in arrays and the first position of lists."));
//set the possible points
updatePossiblePoints();
}
There, we call the put()
instance method of our HashMap
five times, putting a concept and its meaning in studyGuide
each time. This method ends by calling updatePossiblePoints()
to set the possible points, static possiblePoints
, obtainable by a user:
private static void updatePossiblePoints() {
possiblePoints = studyGuide.size();
System.out.println("There are "
+ possiblePoints + " concepts to study.");
}
Note thatpossiblePoints
should always be equal to the number of elements in studyGuide
which we can gather by calling its size()
instance method.
Next, we call printInstructions()
to print our app’s instructions to the console for the user. After that, it’s time to study:
/**
* Starts the study session
*/
private static void study() {
//for scanning the user's keyboard for input
Scanner userInputScanner = new Scanner(System.in);
//to store the user's input
String userInput;
/*the points obtained by the user
for getting the meanings of concepts correct.*/
int userPoints = 0;
/*how many rounds the user takes
to get all of the concept meanings correct*/
int rounds = 1;
//set the default user input because the while loop needs to check it
userInput = "startInConsole";
/*We'll let the user quit at any point in the app,
so we must make sure they haven't quit yet*/
while(userPoints < possiblePoints && !userQuit(userInput, userPoints)) {
//store the keys of the study guide in a immutable Set
final Set<String> concepts = studyGuide.keySet();
for (String concept : concepts) {
Meaning currentConceptMeaning = studyGuide.get(concept);
//make sure currentConceptMeaning is not null
if(currentConceptMeaning == null) {
studyGuide.remove(concept);
//move to next iteration
continue;
}
if(!currentConceptMeaning.userGotCorrect()) {
System.out.printf("\n\t\t" + OUTPUT_SEPERATOR_LINE
+ "\n\n> What is %s?\n\n\n", concept);
if(userInputScanner.hasNextLine()) {
userInput = userInputScanner.nextLine();
}
if (!userQuit(userInput, userPoints)) {
if (currentConceptMeaning.userInputMatches(userInput)) {
currentConceptMeaning.markUserGotCorrect();
userPoints++;
System.out.printf("\n> CORRECT! %s means: %s",
concept, currentConceptMeaning.getMeaning());
} else {
System.out.printf("\n> WRONG! %s means: %s",
concept, currentConceptMeaning.getMeaning());
}
}
}
}
System.out.println(OUTPUT_SEPERATOR_LINE);
System.out.printf("\n> You have %d of %d possible points at the end of round %d. ",
userPoints, possiblePoints, rounds);
//make sure the user hasn't scored all of the possible points
if(userPoints < possiblePoints) {
System.out.println("\n> Type anything to continue "
+ "OR remove to remove a concept \"x\" or \"quit\" to quit?");
if(userInputScanner.hasNextLine()) {
userInput = userInputScanner.nextLine();
if(userInput.toLowerCase().equals("remove")) {
System.out.println("\n> Remove which concept?");
if(userInputScanner.hasNextLine()) {
removeConcept(userInputScanner.nextLine());
}
}
}
} else break; //break out of the loop because the user is done
}
System.out.println(OUTPUT_SEPERATOR_LINE);
System.out.println("Congrats! You got all the meanings correct.");
}
We start studying by calling the study()
method. This method declares a couple of variables to hold information about the user’s study session. We initialize a Scanner
as userInputScanner
to capture the user’s input, which will be stored in userInput
. The user’s accumulated points will be stored in userPoints
and incremented by 1 each time the user provides the right meaning for a concept.
We use a while
loop to continue prompting the user with concepts so long as they haven’t obtained the possible points and they do not enter “x” or “quit” to quit.
At the top of the while
loop, we must store the Set
of keys in studyGuide
in a constant by calling its keySet()
instance method so that we can iterate over it. For each key, we get its value by calling the get()
instance method of studyGuide
and passing it the key. The value returned and stored in currentConceptMeaning
is a Meaning
instance representing the meaning of a concept. So long as the currentConceptMeaning
is not null
, we let the user take a guess at what they think it is. If the userInputMatches()
instance method of currentConceptMeaning
returns true
, we call its markUserGotCorrect()
instance method to set its gotCorrect
instance field to true and increment userPoints
because the user provided the correct meaning for the concept.
At the end of each round, we print out how many points the user has accumulated and we allow them to remove a concept they give up on studying if they haven’t finished the study guide; we do this by calling our removeConcept()
method:
/**
* This allows the user to remove a concept they give up on from the study guide.
* Removes the pair from the HashMap with a key that matches the concept.
* @param concept
*/
private static void removeConcept(String concept) {
Meaning conceptMeaning = studyGuide.get(concept);
if(conceptMeaning != null) {
//make sure the user hasn't already gotten the meaning correct
if(!conceptMeaning.userGotCorrect()){
//remove the concept's key-value pair from the study guide
studyGuide.remove(concept);
System.out.println("Removed \"" + concept + "\" from your study guide.");
/*update the possible points so that it matches
the number of concepts left in the study guide*/
updatePossiblePoints();
} else {
//don't let the user remove a concept they already know
System.out.println("You know \"" + concept + "\", silly. Can't remove.");
}
} else {
System.out.println("\"" + concept + "\" isn't in your study guide.");
}
}
When we call removeConcept()
, it gets the Meaning
value corresponding to the concept in studyGuide
as conceptMeaning
. If the concept is found, we call the userGotCorrect()
instance method of conceptMeaning
to check if the user got the meaning of the concept correct, and if this returns false
, we call the remove()
instance method of studyGuide()
to remove the key-value pair from the HashMap
by the concept as the key. We don’t let the user remove a concept that they already correctly defined because they already received points for it.
Our userQuit()
method checks if the user asked to quit at any point to end the program and print out the user’s points. On the other hand, if the user makes it to the end of the study guide, we call break
to break out of the while
loop. At that point, the app will congratulate the user on a successful study session and the program will end.
Conclusion
Well look at that, we’re all done! We now have a study guide built with a single HashMap
to help us study our Java concepts. We also now know how to use the HashMap
for efficiently storing and manipulating key-value pairs in Java. You’re already on your way to be a master of the maps! Play around with the source code and see how you can make the study guide better. Have questions? Leave a comment and I’ll do my best to answer.
References: Oracle Documentation on Maps Oracle Documentation on HashMaps
Frequently Asked Questions (FAQs) about Java HashMap
What is the difference between HashMap and Hashtable in Java?
Both HashMap and Hashtable are part of Java’s collections framework and are used to store key-value pairs. However, there are some significant differences between the two. The primary difference is that HashMap is not synchronized, meaning it is not thread-safe and cannot be shared between multiple threads without proper synchronization. On the other hand, Hashtable is synchronized and is thread-safe. Another difference is that HashMap allows one null key and multiple null values, while Hashtable does not allow any null keys or values.
How does the get() method work in HashMap?
The get() method in HashMap is used to retrieve a value based on its corresponding key. It takes the key as a parameter and returns the value associated with that key. If the key is not found in the HashMap, it returns null. The get() method uses the hashcode of the key to find the bucket where the key-value pair is stored.
How can I iterate over a HashMap in Java?
There are several ways to iterate over a HashMap in Java. One common method is to use the keySet() method, which returns a Set view of the keys in the HashMap. You can then iterate over this Set using a for-each loop. Another method is to use the entrySet() method, which returns a Set view of the mappings contained in the HashMap. You can then iterate over this Set using a for-each loop and the Map.Entry interface.
What is the initial capacity and load factor of a HashMap?
The initial capacity of a HashMap is the number of buckets in the hash table at the time of its creation. The default initial capacity is 16. The load factor is a measure of how full the hash table is allowed to get before its capacity is automatically increased. The default load factor is 0.75. These values can be adjusted when creating a new HashMap, depending on your specific needs.
How does the put() method work in HashMap?
The put() method in HashMap is used to insert a key-value pair into the map. It takes the key and value as parameters. If the key is not already in the map, the key-value pair is added. If the key is already in the map, the old value is replaced with the new value. The put() method returns the previous value associated with the key, or null if there was no mapping for the key.
How can I remove a key-value pair from a HashMap?
You can remove a key-value pair from a HashMap using the remove() method. This method takes the key as a parameter and removes the corresponding key-value pair from the map. It returns the value that was associated with the key, or null if the key was not in the map.
What happens when two keys have the same hashcode in a HashMap?
In a HashMap, two keys can have the same hashcode, which is known as a hash collision. When this happens, the key-value pairs are stored in a linked list at the same bucket. When you try to retrieve a value using a key, the HashMap uses the key’s hashcode to find the correct bucket and then iterates over the linked list to find the correct key-value pair.
Is HashMap ordered?
No, HashMap is not ordered. It does not maintain any order of its elements, neither based on the insertion order nor based on the keys or values. If you need an ordered map, you can use LinkedHashMap or TreeMap.
Can I store null values in a HashMap?
Yes, you can store null values in a HashMap. In fact, a HashMap can contain one null key and multiple null values.
How can I check if a HashMap contains a specific key or value?
You can check if a HashMap contains a specific key using the containsKey() method, which takes the key as a parameter and returns true if the map contains a mapping for the key. To check if a HashMap contains a specific value, you can use the containsValue() method, which takes the value as a parameter and returns true if the map maps one or more keys to the value.
Lincoln W Daniel is a software engineer who has worked at companies big and small. Receiving internship offers from companies like NASA JPL, he has worked at IBM and Medium.com. Lincoln also teaches programming concepts on his ModernNerd YouTube channel by which he emphasizes relating coding concepts to real human experiences.