HashSet in Java

Java HashSet removes duplicate items automatically. This means you don’t need to write extra code to check for duplicates. It keeps your data clean and unique. Imagine building an e-commerce site where every product needs a unique ID, or managing a list of users currently logged into your application. HashSet makes these tasks simple and efficient.

This guide will help you master Java HashSet.

What is a Java HashSet?

A Java HashSet is a collection that stores unique elements. It does not keep items in any specific order. It uses a hash table to store its items. This lets you add, remove, or check for items quickly. It implements the Set interface, which means it follows the rules for sets: no duplicate elements and no specific order.

How HashSet Works Internally

Understanding how HashSet works inside helps you use it better. A HashSet uses a HashMap internally. Each element you add to a HashSet is stored as a “key” in the internal HashMap. The “value” part of the HashMap entry is a dummy object.

When you add an element:

  • hashCode() Call: The HashSet first calls the hashCode() method on the object you are adding. This method returns an integer number. This number helps determine where to store the object in the hash table (an array).
  • Bucket Calculation: The hashCode is used to find a specific “bucket” or index in the internal array.
  • equals() Call and Collision Handling: If multiple objects have the same hashCode (this is called a “collision”), the HashSet then uses the equals() method to compare the new object with existing objects in that bucket.
    • If equals() returns true for an existing object, it means the new object is a duplicate. The HashSet does not add it.
    • If equals() returns false for all objects in that bucket, the new object is unique and gets added to the bucket.

This process ensures uniqueness. For custom objects, you must correctly override both hashCode() and equals() methods. If you don’t, HashSet might not correctly identify duplicate objects.

Academy Pro

Java Programming Course

Learn Java the right way! Our course teaches you essential programming skills, from coding basics to complex projects, setting you up for success in the tech industry.

16.05 Hrs
3 Projects
Learn Java Programming

Why Use a HashSet?

HashSet helps you manage data efficiently. It offers key benefits for your programs:

  • Unique Elements: A HashSet only stores unique items. If you try to add an item that already exists, the HashSet ignores it. The add() method returns false if the item was a duplicate. This saves you from writing code to check for duplicates manually. For example, if you’re tracking active user sessions, you only want to count each user once. A HashSet handles this automatically.
  • Fast Operations: Adding, removing, or checking if an item is present happens very fast. These operations typically take constant time O(1) on average. This means the time to perform the operation does not significantly increase even if you have millions of items in the HashSet. Imagine quickly checking if a product ID already exists in your inventory system. HashSet makes this check nearly instant.
  • No Order Guarantee: The HashSet does not keep items in any specific order. The order of elements can change over time. If you iterate over a HashSet multiple times, the order might be different each time. If you need ordered items, use a different collection like LinkedHashSet or TreeSet.
  • Null Elements: A HashSet can store one null element. Adding more null elements will not add new entries.

5 Steps to Use Java HashSet

You can use a HashSet in your Java programs with these steps.

Step 1: Create a HashSet

First, you need to create a HashSet object. You tell it what type of items it will store.

Here is how you make a HashSet that stores strings:

import java.util.HashSet; // Import the HashSet class

public class HashSetCreation {
    public static void main(String[] args) {
        // Create a HashSet to store String objects for unique user IDs
        HashSet<String> userIDs = new HashSet<>();
        System.out.println("Empty HashSet for user IDs created: " + userIDs);

        // You can also specify an initial capacity (e.g., 20 elements)
        // This can be useful if you know you'll have many items, like product IDs.
        HashSet<Integer> productIDs = new HashSet<>(100);
        System.out.println("HashSet for product IDs with initial capacity created: " + productIDs);
    }
}

This code creates an empty HashSet called userIDs. The <String> part tells Java that this HashSet will hold String objects. Specifying an initial capacity can improve performance if you know you will add many items, as it reduces the need for the HashSet to resize itself later.

Step 2: Add Elements to a HashSet

You use the add() method to put items into your HashSet. The add() method returns a boolean value: true if the element was added (it was unique), and false if the element was already present (it was a duplicate).

import java.util.HashSet;

public class HashSetAddExample {
    public static void main(String[] args) {
        HashSet<String> loggedInUsers = new HashSet<>();

        // Add users logging into a system
        boolean addedAlice = loggedInUsers.add("Alice");
        boolean addedBob = loggedInUsers.add("Bob");
        boolean addedCharlie = loggedInUsers.add("Charlie");
        boolean addedAliceAgain = loggedInUsers.add("Alice"); // Alice tries to log in again

        System.out.println("Logged-in users: " + loggedInUsers);
        System.out.println("Was 'Alice' added the first time? " + addedAlice);      // true
        System.out.println("Was 'Bob' added? " + addedBob);       // true
        System.out.println("Was 'Alice' added again? " + addedAliceAgain); // false (because she's already logged in)
    }
}

When you run this code, the output shows only “Alice”, “Bob”, and “Charlie” in the HashSet. The addedAliceAgain variable will be false because “Alice” was already present. This ensures each logged-in user is counted only once.

Step 3: Check for Elements in a HashSet

You can check if an item exists in the HashSet using the contains() method. This method returns true if the item is there, and false if it is not. This operation is very fast, usually constant time.

import java.util.HashSet;

public class HashSetContainsExample {
    public static void main(String[] args) {
        HashSet<String> availableCoupons = new HashSet<>();
        availableCoupons.add("FREESHIP");
        availableCoupons.add("SAVE10");
        availableCoupons.add("WELCOME20");

        // Check if a user has a specific coupon code
        boolean hasFreeShip = availableCoupons.contains("FREESHIP");
        boolean hasHolidayDeal = availableCoupons.contains("HOLIDAYDEAL");

        System.out.println("Available coupons: " + availableCoupons);
        System.out.println("Does user have 'FREESHIP'? " + hasFreeShip);    // true
        System.out.println("Does user have 'HOLIDAYDEAL'? " + hasHolidayDeal); // false
    }
}

This code prints true for “FREESHIP” and false for “HOLIDAYDEAL”. This is useful for quickly verifying coupon validity.

Step 4: Remove Elements from a HashSet

To remove an item from a HashSet, use the remove() method. It returns true if the item was successfully removed (meaning it was present in the set), and false if the item was not found in the set. Like add() and contains(), remove() is also a very fast operation.

import java.util.HashSet;

public class HashSetRemoveExample {
    public static void main(String[] args) {
        HashSet<String> shoppingCartItems = new HashSet<>();
        shoppingCartItems.add("Laptop");
        shoppingCartItems.add("Mouse");
        shoppingCartItems.add("Keyboard");
        shoppingCartItems.add("Monitor");

        System.out.println("Shopping cart items before removal: " + shoppingCartItems);

        // Remove an item that the user no longer wants
        boolean removedMouse = shoppingCartItems.remove("Mouse");
        System.out.println("Was 'Mouse' removed? " + removedMouse); // true
        System.out.println("Shopping cart items after removing Mouse: " + shoppingCartItems);

        // Try to remove an item that was never in the cart
        boolean removedHeadphones = shoppingCartItems.remove("Headphones");
        System.out.println("Was 'Headphones' removed? " + removedHeadphones); // false
        System.out.println("Shopping cart items after trying to remove Headphones: " + shoppingCartItems);
    }
}

After running this, “Mouse” is no longer in the HashSet. The output shows true for removing “Mouse” and false for removing “Headphones”. This helps manage unique items in a user’s shopping cart.

Step 5: Iterate Through a HashSet

You often need to go through all items in your HashSet. You can do this using a for-each loop. Since HashSet does not guarantee any order, the elements might appear in a different sequence each time you iterate, especially across different runs or if the set has been modified.

import java.util.HashSet;
import java.util.Iterator; // Optional: for using Iterator directly

public class HashSetIterateExample {
    public static void main(String[] args) {
        HashSet<String> studentNames = new HashSet<>();
        studentNames.add("Alice");
        studentNames.add("Bob");
        studentNames.add("Charlie");
        studentNames.add("David");

        System.out.println("Listing student names (order may vary):");
        // Iterate through elements using a for-each loop
        for (String name : studentNames) {
            System.out.println(name);
        }

        System.out.println("\nUsing an Iterator to process names:");
        // You can also use an Iterator for more control, especially for removing elements during iteration
        Iterator<String> iterator = studentNames.iterator();
        while (iterator.hasNext()) {
            String name = iterator.next();
            System.out.println("Processing: " + name);
            // Example: If you need to remove students whose name starts with 'D'
            // if (name.startsWith("D")) {
            //     iterator.remove(); // Removes "David" safely
            // }
        }
        // System.out.println("\nStudents after potential removal: " + studentNames);
    }
}

This code prints each student name in the HashSet. Remember, the order may not be the same as the order you added them. Using an Iterator is particularly useful if you need to remove elements from the set while looping through it, as directly removing elements in a for-each loop can lead to ConcurrentModificationException.

Important HashSet Methods

HashSet provides many useful methods. Here are some common ones:

  • size(): This method returns the number of items currently in the HashSet.

    HashSet<String> mySet = new HashSet<>();
    mySet.add("User1");
    mySet.add("User2");
    System.out.println("Number of unique users: " + mySet.size()); // Output: 2

  • isEmpty(): This method checks if the HashSet has any items. It returns true if it has no items, false otherwise.

    HashSet<String> mySet = new HashSet<>();
    System.out.println("Is the user list empty? " + mySet.isEmpty()); // Output: true
    mySet.add("Admin");
    System.out.println("Is the user list empty now? " + mySet.isEmpty()); // Output: false

  • clear(): This method removes all items from the HashSet. After calling clear(), the set will be empty.

    HashSet<String> mySet = new HashSet<>();
    mySet.add("Session1");
    mySet.add("Session2");
    System.out.println("Active sessions before clear: " + mySet);
    mySet.clear();
    System.out.println("Active sessions after clear: " + mySet); // Output: [] (all sessions ended)

  • addAll(Collection c): This adds all unique items from another collection to the HashSet. It is useful for merging sets or adding many items at once.

    HashSet<String> onlineUsers = new HashSet<>();
    onlineUsers.add("Alice");
    onlineUsers.add("Bob");

    HashSet<String> newLogins = new HashSet<>();
    newLogins.add("Charlie");
    newLogins.add("Alice"); // Alice is already online

    onlineUsers.addAll(newLogins); // Adds new unique logins to the existing set
    System.out.println("All online users: " + onlineUsers); // Output: [Bob, Charlie, Alice] (order may vary)

  • removeAll(Collection c): This removes all items from the HashSet that are also present in the specified collection c. It performs a set difference operation.

    HashSet<Integer> allProductIds = new HashSet<>();
    allProductIds.add(101); allProductIds.add(102); allProductIds.add(103); allProductIds.add(104);

    HashSet<Integer> soldOutIds = new HashSet<>();
    soldOutIds.add(102); soldOutIds.add(104);

    allProductIds.removeAll(soldOutIds); // Remove sold out products from available
    System.out.println("Available product IDs: " + allProductIds); // Output: [101, 103]

  • retainAll(Collection c): This keeps only the items in the HashSet that are also present in the specified collection c. It removes all other items. This performs a set intersection operation.

    HashSet<String> userInterests = new HashSet<>();
    userInterests.add("Sports"); userInterests.add("Movies"); userInterests.add("Reading"); userInterests.add("Gaming");

    HashSet<String> recommendedCategories = new HashSet<>();
    recommendedCategories.add("Movies"); recommendedCategories.add("Books"); recommendedCategories.add("Sports");

    userInterests.retainAll(recommendedCategories); // Find common interests
    System.out.println("Common interests (for personalized recommendations): " + userInterests); // Output: [Sports, Movies]

Performance Considerations

The performance of HashSet operations (add, remove, contains, size) is generally excellent, often described as O(1) on average. This means the time taken for these operations stays constant regardless of the number of elements in the set.

However, in the worst case (e.g., if many elements have the same hashCode causing many collisions), the performance can degrade to O(n), where n is the number of elements.

Factors affecting performance:

  • hashCode() and equals() Methods: The quality of these methods for the objects stored in the HashSet is crucial. A poorly implemented hashCode() method that returns the same value for many different objects will lead to frequent collisions, degrading performance.
  • Initial Capacity: The initial capacity is the number of buckets in the underlying hash table. If you know roughly how many elements you will store, setting an appropriate initial capacity can prevent the HashSet from resizing too often.

    Resizing involves rehashing all existing elements, which can be a slow operation. The default initial capacity is 16.
  • Load Factor: The load factor (default 0.75) determines when the HashSet will resize. When the number of elements divided by the current capacity exceeds the load factor, the HashSet resizes (usually doubles its capacity) and rehashes all elements.

    A lower load factor means more space used but fewer collisions. A higher load factor saves space but increases collision probability, potentially slowing down operations. You can specify both initial capacity and load factor when creating a HashSet:
HashSet<String> mySet = new HashSet<>(100, 0.9f); // Capacity 100, load factor 0.9

Thread Safety with HashSet

A HashSet is not thread-safe. This means if multiple threads (parts of your program running at the same time) try to modify a HashSet concurrently (add, remove, clear, etc.), you might encounter unexpected behavior or errors like ConcurrentModificationException.

If your application needs to use a HashSet in a multi-threaded environment where multiple threads will modify it, you need to ensure thread safety. Here are common ways:

  • Synchronized Wrapper: You can use Collections.synchronizedSet() to get a thread-safe wrapper around your HashSet. All operations on this synchronized set will be automatically synchronized.

    import java.util.Collections;
    import java.util.HashSet;
    import java.util.Set;

    public class SynchronizedHashSetExample {
    public static void main(String[] args) {
    // Create a synchronized HashSet
    Set<String> safeSet = Collections.synchronizedSet(new HashSet<>());

    // Now, 'safeSet' can be accessed by multiple threads safely
    safeSet.add("Item A");
    safeSet.add("Item B");
    System.out.println("Synchronized Set: " + safeSet);
    }
    }

    While this makes operations thread-safe, it can introduce performance bottlenecks if many threads frequently access the set, as it locks the entire set for each operation.


  • ConcurrentHashMap.newKeySet() (Java 8+): For more advanced and scalable concurrent scenarios, especially when building concurrent applications, ConcurrentHashMap offers a newKeySet() method.

    This method returns a Set view backed by a ConcurrentHashMap. This provides better concurrency performance than Collections.synchronizedSet() because it uses fine-grained locking.

    import java.util.Set;
    import java.util.concurrent.ConcurrentHashMap;

    public class ConcurrentHashSetExample {
    public static void main(String[] args) {
    // Create a concurrent set using ConcurrentHashMap.newKeySet()
    Set<String> concurrentSet = ConcurrentHashMap.newKeySet();

    // This set can be safely accessed and modified by multiple threads
    concurrentSet.add("ThreadSafe Item 1");
    concurrentSet.add("ThreadSafe Item 2");
    System.out.println("Concurrent Set: " + concurrentSet);
    }
    }

    This is generally the preferred approach for high-concurrency scenarios in modern Java.


Read in Detail: Synchronization in Java

HashSet vs. Other Collections

Choosing the right collection is crucial for efficient programming. Here’s how HashSet compares to other common Java collections:

HashSet vs. ArrayList:

  • Uniqueness: HashSet stores only unique elements. ArrayList allows duplicates.
  • Order: HashSet does not maintain insertion order. ArrayList maintains insertion order.
  • Access: HashSet provides fast O(1) average-time for add, remove, contains. ArrayList provides fast O(1) for getting elements by index, but add and remove at specific positions can be O(n), and contains is O(n).
  • Use Case: Use HashSet when you need a collection of unique items and order is not important (e.g., unique product codes). Use ArrayList when you need an ordered list and duplicates are allowed (e.g., a list of all transactions).

HashSet vs. TreeSet:

  • Order: HashSet has no defined order. TreeSet stores elements in a sorted order (natural order or via a Comparator).
  • Performance: HashSet offers O(1) average-time operations. TreeSet offers O(log n) time for add, remove, contains because it uses a balanced binary search tree.
  • Underlying Structure: HashSet uses a hash table. TreeSet uses a TreeMap.
  • Use Case: Use HashSet for speed when order doesn’t matter. Use TreeSet when you need unique, sorted elements (e.g., a list of unique, sorted user scores).

HashSet vs. LinkedHashSet:

  • Order: HashSet has no defined order. LinkedHashSet maintains the insertion order of elements.
  • Performance: Both offer similar O(1) average-time performance. LinkedHashSet has slightly higher overhead due to maintaining a linked list for order.
  • Underlying Structure: Both use hash tables, but LinkedHashSet also has a doubly-linked list running through its entries to preserve insertion order.
  • Use Case: Use HashSet for pure uniqueness and speed. Use LinkedHashSet when you need unique elements but also want to retrieve them in the order they were added (e.g., the order in which items were added to a shopping cart for display).

Common Use Cases for HashSet

HashSet is very useful in many programming situations:

  • Removing Duplicates from a List: This is a primary use. If you have a list of items (e.g., email addresses from multiple sources) and you want only the unique ones, you can add them all to a HashSet.

    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.List;

    public class DeduplicateEmails {
    public static void main(String[] args) {
    List<String> allEmails = new ArrayList<>();
    allEmails.add("user1@example.com");
    allEmails.add("user2@example.com");
    allEmails.add("user1@example.com"); // Duplicate email
    allEmails.add("user3@example.com");

    System.out.println("Original email list: " + allEmails);

    HashSet<String> uniqueEmails = new HashSet<>(allEmails); // Converts list to set, removing duplicates
    System.out.println("Unique email list: " + uniqueEmails);
    }
    }

  • Checking for Presence Quickly: When you need to frequently check if an item exists in a large collection, HashSet is much faster than a List. For example, quickly checking if a username is already taken during registration.
  • Implementing Tags or Categories: If an item can have multiple unique tags (e.g., a blog post with “Java”, “Programming”, “Tutorial” tags), a HashSet<String> is perfect for storing them.
  • Counting Unique Visitors: In web analytics, if you need to count how many unique users visited your site in a day, you can add each user’s ID to a HashSet. The size() of the HashSet will give you the count of unique visitors.
  • Set Operations: HashSet is ideal for mathematical set operations like union (addAll), intersection (retainAll), and difference (removeAll). This is common in data analysis or filtering.

HashSet Best Practices

Follow these tips to use HashSet effectively:

  • Choose the Right Collection: Use HashSet when you need to store unique items and the order does not matter. If order is important, consider LinkedHashSet or TreeSet. If duplicates are allowed and order matters, an ArrayList is a better choice.
  • Override hashCode() and equals(): For custom objects you store in a HashSet, always override both hashCode() and equals() methods correctly. This is fundamental for HashSet to work as expected, especially for identifying duplicates.

    Tools like your IDE (e.g., IntelliJ IDEA, Eclipse) can auto-generate these methods for you.
  • Consider Initial Capacity and Load Factor: For large HashSets or performance-critical applications, adjust the initial capacity and load factor. A good rule of thumb is to set the initial capacity to (expected_number_of_elements / load_factor) + 1. This reduces the number of rehash operations and improves performance.
  • Avoid Modifying Elements: Do not modify the fields of an object after it has been added to a HashSet if those fields are used in the object’s hashCode() or equals() methods.

    Modifying them can change the object’s hash code, making it “lost” in the set, so you can’t find or remove it correctly. If an object needs to be mutable and stored in a HashSet, ensure that the fields used in hashCode() and equals() are immutable (e.g., final fields).
  • Understand Iteration Order: Always remember that HashSet does not guarantee any iteration order. Do not write code that depends on the order of elements when looping through a HashSet.
Avatar photo
Great Learning Editorial Team
The Great Learning Editorial Staff includes a dynamic team of subject matter experts, instructors, and education professionals who combine their deep industry knowledge with innovative teaching methods. Their mission is to provide learners with the skills and insights needed to excel in their careers, whether through upskilling, reskilling, or transitioning into new fields.

Academy Pro Subscription

Grab 50% off
on Top Courses - Free Trial Available

×
Scroll to Top