Search⌘ K
AI Features

Working with ArrayList, HashSet, and HashMap

Explore how to use Java Collections Framework's core classes ArrayList HashSet and HashMap to handle dynamic data storage enforce uniqueness and manage key value associations. Understand when to choose each collection type and how their operations impact performance and code clarity.

Arrays are the foundational building blocks of data storage in Java, but they are rigid. You must define their size upfront, and that size cannot change. In the real world, data is dynamic: users sign up, shopping carts grow, and log files expand. We rarely know exactly how many items we will need to store when we write the code.

To manage varying data storage and retrieval needs, we use the Java Collections Framework. The framework offers many specialized tools, but for most use cases, you’ll rely on three core implementations: ArrayList for dynamic arrays, HashSet for unique collections, and HashMap for key-value pairs. Choosing the right implementation affects both the complexity of your code and its runtime performance.

The resizable array (ArrayList)

The ArrayList is the most widely used collection in Java. You can think of it as a smart array that automatically resizes itself as you add or remove elements. Like a standard array, it maintains a specific order (insertion order) and allows you to access elements instantly if you know their position (index).

To use an ArrayList, we must specify the type of data it will hold using generics the <Type> syntax. This enforces type safety, ensuring we do not accidentally store an Integer in a list intended for String values.

In the example below, we manage a dynamic list of todo items. Notice that we can add duplicates, and the list preserves the order in which we added them. Beyond just adding items, ArrayList provides methods to modify items at specific positions (set), check the list size (size), and clear all data (clear).

Java 25
import java.util.ArrayList;
public class ListExample {
public static void main(String[] args) {
// Create a list restricted to String objects
ArrayList<String> todoList = new ArrayList<>();
// Adding elements
todoList.add("Buy groceries");
todoList.add("Walk the dog");
todoList.add("Buy groceries"); // Duplicates are allowed
// Accessing by index (0-based)
String firstItem = todoList.get(0);
// Modifying an element at a specific index
todoList.set(1, "Feed the cat");
// Checking size
System.out.println("Items to do: " + todoList.size());
System.out.println("First item: " + firstItem);
System.out.println("Full list: " + todoList);
}
}
  • Line 6: We declare an ArrayList that can only hold String objects. The <> operator on the right side (the diamond operator) infers the type from the left.

  • Lines 9–11: We use add() to append items. The ArrayList expands automatically to accommodate them.

  • Line 14: We retrieve an item using get(int index). This is an extremely fast operation (constant time, or O(1)O(1)).

  • Line 17: We replace the element at index 1 using set(). This overwrites the existing value (“Walk the dog”).

  • Line 20: size() returns the current number of elements (3), not the capacity of the backing array.

Performance note: ArrayList is optimized for reading data by index. However, if you need to find a specific element without knowing its index (e.g., “Does this list contain ‘Feed the cat’?” using contains()), the list must scan elements sequentially from the beginning. As the list grows, these searches become slower.

Ensuring uniqueness with HashSet

Sometimes, the order of data matters less than its uniqueness. If you are tracking a list of active user IDs or unique words in a document, you do not want duplicates. An ArrayList would require you to manually check for existence before every addition. The HashSet handles this automatically.

A HashSet represents a mathematical set. It enforces uniqueness and, unlike a list, it does not guarantee any specific order. If you print a HashSet, the elements may appear in a completely different order than you inserted them.

Java 25
import java.util.HashSet;
public class SetExample {
public static void main(String[] args) {
HashSet<Integer> idSet = new HashSet<>();
// Adding elements
idSet.add(101);
idSet.add(102);
boolean wasAdded = idSet.add(101); // Attempting to add a duplicate
// Checking for existence
boolean hasId = idSet.contains(102);
System.out.println("Duplicate added? " + wasAdded);
System.out.println("Contains 102? " + hasId);
System.out.println("Set content: " + idSet);
}
}
  • Line 5: We create a HashSet restricted to Integer objects.

  • Line 10: We attempt to add 101 again. The add() method returns boolean: true if the item was new and added, false if it was already present. Here, it returns false.

  • Line 13: We use contains() to check if a value exists.

Performance note: This is where HashSet shines. Thanks to a mechanism called hashing, checking contains() is nearly instantaneous, regardless of whether the set has ten items or ten million. If you strictly need to check for existence and do not care about order, HashSet is significantly faster than ArrayList.

Key-value associations with HashMap

We often need to associate one piece of data with another, such as looking up a phone number by a name or a product price by its SKU. HashMap allows us to store data as key-value pairs.

In a HashMap:

  1. Keys must be unique (like a HashSet).

  2. Values can be duplicated.

  3. We retrieve values using the key, not an index.

Java 25
import java.util.HashMap;
public class MapExample {
public static void main(String[] args) {
// Map associating a String (Username) with an Integer (Score)
HashMap<String, Integer> scores = new HashMap<>();
// Adding pairs
scores.put("Alice", 50);
scores.put("Bob", 85);
scores.put("Charlie", 50); // Duplicate value is allowed
// Updating a value (Keys must be unique)
scores.put("Alice", 60); // Overwrites 50 with 60
// Retrieving a value by key
Integer aliceScore = scores.get("Alice");
Integer unknownScore = scores.get("David"); // Returns null
// Checking for a key
boolean hasDavid = scores.containsKey("David");
System.out.println("Alice's Score: " + aliceScore);
System.out.println("David's Score: " + unknownScore);
System.out.println("Has David? " + hasDavid);
System.out.println("All Scores: " + scores);
}
}
  • Line 6: We declare a HashMap where the key is a String and the value is an Integer.

  • Lines 9–11: We use put() to store pairs. Note that “Alice” and “Charlie” have the same score (value), which is permitted.

  • Line 14: We call put() again with the existing key “Alice”. This updates her score from 50 to 60.

  • Line 17: We retrieve the value associated with “Alice” using get(). This is an efficient, constant-time lookup.

  • Line 18: If we request a key that does not exist, get() returns null.

  • Line 21: containsKey() efficiently checks if a key exists without retrieving the value.

Removing individual elements

Each collection handles removal differently. ArrayList is unique because it allows removal by either index or object, while HashSet and HashMap rely on the object or key.

  • ArrayList: It has two remove methods.

    • remove(int index): Removes the item at that specific position.

    • remove(Object o): Removes the first occurrence of that specific object.

  • HashSet: remove(Object o) finds and removes the item.

  • HashMap: remove(Object key) removes the key and its value.

In this example, we will remove items from each collection and verify the removal by printing the updated collection or checking for existence.

Java 25
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HashMap;
public class RemovalExample {
public static void main(String[] args) {
// 1. Setup Data
ArrayList<String> languages = new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.add("C++");
HashSet<Integer> nums = new HashSet<>();
nums.add(1);
nums.add(99);
HashMap<String, String> config = new HashMap<>();
config.put("Theme", "Dark");
config.put("Version", "1.0");
// 2. Removal Logic
languages.remove(1); // Removes "Python" (by index)
languages.remove("Java"); // Removes object "Java"
nums.remove(99); // Removes the integer 99 from the Set
config.remove("Version"); // Removes the key "Version"
// 3. Verification
System.out.println("Languages: " + languages); // Should only contain "C++"
System.out.println("Nums has 99? " + nums.contains(99));
System.out.println("Config has Version? " + config.containsKey("Version"));
}
}
  • Line 22: languages.remove(1) removes the element at index 1 (“Python”).

  • Line 23: languages.remove("Java") searches for the string “Java” and removes the first match.

  • Line 25: nums.remove(99) finds the value 99 in the set and removes it.

  • Line 27: config.remove("Version") removes the entry where the key is “Version”.

  • Lines 30–32: We print the collections and use contains and containsKey to confirm the items are gone.

Bulk operations

Sometimes you need to manipulate the entire collection at once rather than one item at a time. The Collections Framework provides standard methods for this:

  • addAll(Collection c): Adds all elements from another collection.

  • clear(): Removes all elements, resetting the size to 0.

  • isEmpty(): Returns true if the collection has no elements.

Java 25
import java.util.HashSet;
import java.util.List;
public class BulkExample {
public static void main(String[] args) {
HashSet<Integer> nums = new HashSet<>();
nums.add(1);
// Bulk Addition
List<Integer> newNums = List.of(2, 3, 4);
nums.addAll(newNums); // Merges the list into the set
System.out.println("After addAll: " + nums);
// Clearing Data
nums.clear();
// Verification
System.out.println("Is set empty? " + nums.isEmpty());
}
}
  • Line 10: We create a temporary list of integers using List.of.

  • Line 11: nums.addAll(newNums) adds 2, 3, and 4 to the HashSet in a single operation. Duplicate values are ignored.

  • Line 16: clear() removes every element from the set.

  • Line 19: isEmpty() confirms the set size is now 0.

Choosing the right collection

Selecting the correct collection is a trade-off between how you need to store the data (ordered vs. unique) and how you need to access it (by index vs. by search).

Feature

ArrayList

HashSet

HashMap

Primary Use

Ordered list

Unique groups

Key-value lookups

Access Style

By index (0, 1, ...)

By object

By key

Duplicates?

Allowed

Not allowed

Allows for values only

Ordered?

Yes (insertion order)

No guarantee

No guarantee

Performance

Fast access by index

Fast existence check

Fast lookup by key

Use an ArrayList when position matters or when you just need a simple container. Use HashSet when you must prevent duplicates. Use HashMap when you need to look up information based on a unique identifier.

ArrayList, HashSet, and HashMap are the most commonly used general-purpose collection implementations in Java. Although the Collections Framework includes other implementations, such as LinkedList and TreeMap, ArrayList, HashSet, and HashMap are typically preferred because they offer predictable performance and straightforward APIs for most general-purpose scenarios. Understanding how their add, get, and remove operations behave in terms of time complexity and constraints is essential for choosing the right collection and writing efficient Java code.