HashSet in Java is a collection class used to store unique elements, where duplicates are not allowed and the order of elements is not preserved. It is part of the Java Collections Framework and offers fast performance for common operations like adding, removing, and searching using hashing. For example, if you’re building a login system and want to keep track of active users without duplicates, HashSet or LinkedHashSet in Java can help.
In Java, the HashSet class is widely used for storing unique elements. This article will guide you through HashSet Java examples, how to iterate a HashSet, and explain the difference between HashSet and HashMap in Java.
Table of Contents:
What is a HashSet in Java?
HashSet is a class in Java that is used to store a group of unique elements. It is a part of the Java Collections Framework and is present in the java.util package.
HashSet is made up of two words, Hash and Set, which means the following:
- Hash: It uses the technique of hashing to store elements, which allows the fast operations like searching, inserting, and deletion.
- Set: The set data structures used here are the same as those used in maths, which store unique elements. HashSet in Java does not maintain the order of the elements that are stored in it.
HashSet does not provide a get() method like lists or maps, so if you need to retrieve elements, you must iterate over the HashSet in Java using loops or a Java HashSet iterator.
When comparing HashSet with other set implementations like LinkedHashSet and TreeSet, the key differences lie in ordering and performance. HashSet is faster for most operations, but does not maintain order. In contrast, LinkedHashSet maintains insertion order while TreeSet keeps elements sorted but is slower.
Looking at all these features, let us summarize where you can use HashSet in Java
When to Choose HashSet in Java
You should use a HashSet when you need to ensure no duplicate values exist, or when you do not care about the order of the elements. You should also prefer HashSet in Java over any other set data structures when the fast performance of add, remove, and search is required. It is also advisable to use them when working with large datasets like user IDs, product SKUs, or unique tags.
For example, managing a list of active user sessions or unique product names is an ideal Java HashSet example. If you need key-value storage, consider using HashMap, but understanding the difference between HashSet and HashMap in Java is important. We will discuss that in the further sections.
Core Syntax & Declaration of HashSet in Java
In Java, you declare a HashSet using the HashSet class from the java.util package. You should also specify the type of elements it will store using generics. Below is a detailed explanation of it.
Syntax:
HashSet<Type> setName = new HashSet<>();
This means you’re creating a set that can store elements of the specified Type.
Let us understand this now with the help of a basic Java HashSet example.
Example:
Output:
Explanation:
In the above code, a HashSet of type String is created. Even though “Java” is added twice, only one instance is stored because HashSet does not allow duplicates. Additionally, a single null value can be added to the set.
Note: HashSet does not provide a get() method, unlike lists or maps. To access elements, you need to iterate the HashSet in Java using a loop or a Java HashSet iterator.
Internal Working of HashSet
Before moving further, let’s understand the internal working of a HashSet in Java. Knowing this will help you know why it doesn’t allow duplicates and how it achieves fast performance.
So, let us see what happens when you add an element to the HashSet in Java. The following steps take place:
1. Compute the Hash Code
Every object in Java has a method called hashCode(). When an element is added to a HashSet, it calls the hashCode() method to generate a numeric hash value. This value helps determine where the object should be stored internally.
2. Apply Hashing
In this step, the bucket index is searched where an element can be stored. Buckets are like slots in an The generated hash code is then passed through a hashing function to calculate the index of the bucket where the element will be placed. Think of a bucket as a slot in an internal array. These buckets ensure constant-time performance for common operations like add, remove, and contains.
3. Check for Duplicates using equals()
This step is important since the Java HashSet does not contain any duplicate elements. If the calculated bucket already contains an object, the equals() method is used to compare the new element with existing ones. If equals() returns true, the object is considered a duplicate and will not be added to the HashSet. This combination of hashCode() and equals() ensures that no duplicate elements are stored.
4. Store in an Internal HashMap
HashSet does not work on its own and internally uses a HashMap to store its elements. Each element added to the HashSet becomes a key in the underlying HashMap. The value associated with the key is a dummy object whose value is constant, commonly named PRESENT, that is used only to fill the map. This is why the HashSet offers such a fast performance; it uses the power of hashing and maps.
Note: Why There Is No get() Method
Unlike lists or maps, HashSet does not provide a get() method. This is because sets are not designed to access elements by position or key, as the element in itself is a key. If you need to retrieve elements, you can iterate over the HashSet in Java using a loop or a Java HashSet iterator.
Hierarchy of HashSet
In Java, HashSet belongs to the Collections Framework and inherits its behavior through several abstract classes and interfaces. Understanding this hierarchy is essential for using it effectively and comparing it with other implementations like LinkedHashSet or TreeSet.
Below is a diagram that illustrates the hierarchy and relationship of HashSet within the Java Collections Framework:
In the above figure, HashSet extends from the Set interface and ultimately derives from Object. Here’s the direct hierarchy:
java.lang.Object
↳ java.util.AbstractCollection<E>
↳ java.util.AbstractSet<E>
↳ java.util.HashSet<E>
Now, let us see each level in detail.
1. Object
This is the root of all Java classes. Every Java class, including HashSet, inherits common methods like toString(), equals(), and hashCode() from Object.
2. AbstractCollection<E>
It provides the basic implementation for methods in the Collection interface, which helps reduce coding effort for collection classes like HashSet in Java.
3. AbstractSet<E>
This class ensures that all subclasses (like HashSet and LinkedHashSet in Java) maintain the property of storing only unique elements.
4. HashSet<E>
This is the actual class you use to create a set of unique items. It implements the Set
interface using a hashing mechanism, which makes operations like add, remove, and search extremely fast. Note that HashSet does not maintain any order, unlike LinkedHashSet.
Master Java Today - Accelerate Your Future
Enroll Now and Transform Your Future
Features of HashSet in Java
HashSet in Java comes with several useful features that make it a popular choice for storing unique elements. Below are some of the key features:
- Efficient Performance: HashSet provides constant time performance (O(1)) for basic operations like add, remove, and contains, assuming a good hash function distribution.
- Supports Generics: HashSet supports generics, which allows you to specify the type of elements it stores. This enables type safety at compile-time.
- Part of Java Collection Framework: HashSet is a member of the Java Collection Framework and provides functionalities for storing collections of unique elements.
- Implements the Set Interface: It implements the
Set
interface, ensuring that no duplicate elements are allowed.
- Internally backed by HashMap: HashSet uses a HashMap internally to store elements, which ensures fast performance for common operations such as add(), remove(), and contains().
- No Order Guarantee: HashSet does not maintain the insertion order of elements. Items are stored based on their hash code values.
- Allows One Null Element: HashSet allows a single
null
element to be stored.
- Not Thread-Safe (Non-Synchronized): By default, HashSet is not synchronized. If used in multithreaded environments, external synchronization is required.
Methods in HashSet Java
In this section, we will look at the various methods of HashSet in Java with the Big-O complexity of each HashSet method.
Method |
Description |
add(E e) |
Adds the specified element to the set if it is not already present. |
remove(Object o) |
Removes the specified element from the set if it is present. |
contains(Object o) |
Returns true if the set contains the specified element. |
isEmpty() |
Returns true if the set contains no elements. |
size() |
Returns the number of elements in the set. |
clear() |
Removes all elements from the set. |
iterator() |
Returns an iterator over the elements in the set. |
toArray() |
Returns an array containing all of the elements in the set. |
clone() |
Returns a shallow copy of the HashSet instance. |
equals(Object o) |
Compares the specified object with this set for equality. |
hashCode() |
Returns the hash code value for this set. |
retainAll(Collection<?> c) |
Retains only the elements in this set that are contained in the specified collection. |
addAll(Collection<? extends E> c) |
Adds all of the elements in the specified collection to this set. |
removeAll(Collection<?> c) |
Removes from this set all of its elements that are also contained in the specified collection. |
Constructors of the HashSet Class
To use HashSet, you need to first need to create an object using teh HashSet Class. The HashSet in Java class provides multiple constructors that allow flexibility in how a set is created and initialized. These constructors help define the initial capacity, load factor, or even initialize a HashSet from an existing collection. Understanding these options is useful when optimizing memory or performance in real-world scenarios.
Let’s explore the available constructors and their specific use cases:
Constructor |
Description |
HashSet() |
Creates empty HashSet with default initial capacity (16) and load factor (0.75) |
HashSet(int initialCapacity) |
Creates empty HashSet with specified initial capacity and default load factor (0.75) |
HashSet(int initialCapacity, float loadFactor) |
Creates empty HashSet with specified initial capacity and load factor |
HashSet(Collection<extends E> c) |
Creates HashSet containing elements from specified collection (removes duplicates) |
Key Operations & Code Examples
Working with a HashSet in Java involves a few important operations like adding, removing, and accessing elements. This section will discuss each operation with practical Java HashSet examples. You will also learn how to iterate through a HashSet using loops and the Java HashSet iterator. Let’s explore the key operations:
1. Adding Elements in HashSet
To add elements to a HashSet in Java, you can use the add() method to add an element to it. Further, if you add a duplicate element to it, it will be ignored automatically, ensuring all elements remain unique.
Example:
Output:
Explanation:
In the above Java code, the elements are added to the Java HashSet. The duplicate element, “B”, is ignored, and only the unique elements are added.
Get 100% Hike!
Master Most in Demand Skills Now!
2. Removing Elements in HashSet
You can remove a single element from a HashSet in Java using the remove() method. If you want to remove all the elements, you can use the clear() method.
Example:
Output:
Explanation:
In the above Java code, firstly, the elements are added to the HashSet. Then, element 20 is removed from it by using the numbers.remove() method, after which the numbers.clear() method is used to remove all the elements from the hash set.
3. Iterating through the HashSet
To traverse a HashSet, you can use either a for-each loop or the iterator() method. These methods help you access each item without relying on index positions.
Example:
Output:
Explanation:
This example demonstrates how to iterate HashSet Java
using both a for-each loop and the java HashSet iterator.
HashSet is fast because instead of checking every item one by one in it, it directly goes to the correct element using the hash code of that item. This mechanism allows constant time performance (on average) for key operations such as:
- add() – to insert an element
- remove() – to delete an element
- contains() – to check if an element exists
Let’s break it down with more detail:
Main operations in the HashSet and their speed
Operation |
Average Time Complexity |
Worst-case Time Complexity |
add() |
O(1) – Insertion is generally very fast |
O(n) – Can slow down due to collisions |
remove() |
O(1) – Constant time deletion performance |
O(n) – Slower if too many duplicates exist |
contains() |
O(1) – Quick lookup by hash |
O(n) – Poor hashCode causes slow search |
size() |
O(1) – Retrieves total elements quickly |
O(1) – Same for worst case |
Note: O(1) means the time it takes does not increase even if there are 100 or 1000 items. But sometimes it can become slower, i.e., O(n), when more than one item goes to the same shelf.
Understanding Load Factor and Resizing
The load factor in a Java HashSet is a critical value that directly affects performance and memory usage. It determines when the HashSet should resize, that is, when to increase the number of buckets (internal storage slots).
What is Load Factor?
By default, the load factor in Java is 0.75 (75%), which means:
- If 75% of the current capacity is filled with elements,
- The HashSet automatically resizes (typically doubles in capacity),
- And all existing elements are rehashed and placed into new buckets.
This resizing ensures that performance remains fast by minimizing collisions (when multiple elements go into the same bucket).
Why Does Load Factor Matter?
A well-chosen load factor helps balance between memory usage and speed:
Load Factor |
Memory Usage |
Speed / Performance |
High (e.g., 0.9) |
Lower |
Slower (more collisions) |
Low (e.g., 0.5) |
Higher |
Faster (fewer collisions) |
So, a higher load factor saves memory but can slow down operations like add(), remove(), and contains(), because of increased collisions. A lower load factor improves performance but consumes more memory due to more empty buckets.
If you know that you have to store many items, you should have set the size of the HashSet more, like
HashSet<String> set = new HashSet<>(1000);
Internally, HashSet uses a HashMap, where each element becomes a key. The performance of iteration in a HashSet depends on both:
- The number of elements, and
- The number of buckets (capacity).
A HashSet starts with a fixed number of buckets. If the size of the HashSet gets full, it creates a bigger capacity and moves all the items to the new buckets. This process is known as resizing, and it takes the time of about O(n) time.
So, if you set an initial capacity too high or a load factor too low, you’ll waste memory and hurt iteration performance. Conversely, if your load factor is too high, you’ll reduce memory usage but slow down searching and insertion.
When resizing happens:
- All elements must be rehashed and redistributed into the new, larger table.
- This operation takes O(n) time, which can briefly slow down performance.
- But it’s a necessary trade-off to maintain fast average-case performance.
That’s why choosing the right initial capacity is crucial if you know you’ll be storing many elements
Why Is HashSet Fast?
The primary reason why HashSet in Java performs so efficiently lies in its internal architecture, which uses hashing to store and access elements. As discussed earlier, operations like adding, searching, or removing elements are performed using the element’s hashCode() and equals() methods. This allows HashSet to skip linear searches and directly access the correct bucket, making it significantly faster than other collections for these operations, especially when dealing with large datasets. For a deep dive into this mechanism, refer to the Internal Working of HashSet section above.
The higher the load factor, the less memory is used, and it becomes slower. The lower the load factor, the more memory is used, and the faster the operation..
Why HashSet Can Become Slow?
The HashSet can sometimes become slow for the following reasons.
- Hash Collisions: If different elements return the same hash code, they go to the same shelf because of which means searching takes longer.
- Frequent Resizing: If the HashSet has more elements than expected, then it resizes itself, which is a slow process.
- Poor hashCode() Implementation: The hash code method should be such that it returns a unique value every time, if not everything will go in one bucket, which will make the searching slow.
Difference Between Hashset and Hashmap in Java
In Java, both HashSet and HashMap are part of the java.util package and are widely used for storing data using hashing. However, they serve different purposes and have distinct internal workings. While a HashSet is used to store a collection of unique elements, a HashMap stores key-value pairs where each key must be unique. Understanding the difference between HashSet vs HashMap in Java is crucial for choosing the right collection based on your use case, whether you want to enforce uniqueness or perform key-based access.
Feature |
HashSet |
HashMap |
Package |
Part of the java.util package for storing unique elements |
Part of the java.util package for key-value data mapping |
Implements |
Implements Set interface to store only unique elements |
Implements Map interface to map keys with their values |
Storage Structure |
Stores only single values without any key associations |
Stores data as key and value pairs inside a map |
Underlying Implementation |
Internally backed by HashMap to manage elements efficiently |
Internally uses a hash table for faster key lookup |
Duplicates Allowed |
Does not allow duplicate elements to be added again |
Allows duplicate values but does not allow duplicate keys |
Null Handling |
Allows only one null element to be stored internally |
Allows one null key and many null values in total |
Insertion Order |
Does not preserve insertion order of elements automatically |
Does not preserve order of keys or inserted value entries |
Performance Focus |
Optimized for checking presence of unique elements quickly |
Optimized for fast key-based access, insertion, deletion |
Common Use Case |
Use when unique values are needed without any key mapping |
Use when mapping between keys and values is required |
Example Declaration |
HashSet<String> set = new HashSet<>(); |
HashMap<String, Integer> map = new HashMap<>(); |
Best Practices for Using HashSet
- You should use a HashSet only when you need to store unique elements.
- If you store the custom objects in the HashSet, override both equals() and hashCode() to ensure that the HashSet detects the duplicates correctly.
- If you know the approximate number of elements to be stored in the HashSet, then set its initial capacity so that resizing does not occur.
- Use the contains() method to check if the element exists or not in the HashSet.
- HashSet only allows one null to be stored in it. Hence, storing more than one null can lead to confusion.
Limitations of HashSet and When Not to Use It
- HashSet does not maintain the order of the elements; hence, insertion order is not preserved. Hence, do not use HashSet when you need to preserve the insertion order.
- When multiple threads access the HashSet without synchronization, it can cause unexpected behaviour.
- The performance of the HashSet directly depends on the hashCode(), which can lead to incorrect behavior, e.g., duplicate elements not being detected.
- Do not use the HashSet when you need to store the elements in sorted order.
- Do not use HashSet when memory usage is important.
Unlock Your Future in Java
Start Your Java Journey for Free Today
Useful Resources
Conclusion
HashSet is a way to store unique elements in Java. It is fast, easy to use, and does not allow duplicate values. It does not keep the order of elements and allows only one null value. It works well when you need quick retrieval and no order of the elements. HashSet internally uses the HashMap to store its elements. It should be used only when uniqueness matters most. It is part of the Java Collections Framework and is commonly used in everyday coding.
If you want to learn more about this topic, you can refer to our Java course.
HashSet in Java – FAQs
Q1. What is the difference between HashMap and HashSet?
The key difference is that HashMap stores key-value pairs, while HashSet stores only unique values. Internally, HashSet uses a HashMap where each element is stored as a key with a constant dummy value. Also, HashMap allows mapping, whereas HashSet only ensures uniqueness.
Q2. What is the difference between a Set and a HashSet?
The main difference is that Set is an interface in Java that defines a collection of unique elements, while HashSet is a concrete implementation of the Set interface that uses a hash table for storage. HashSet does not maintain insertion order, whereas other Set implementations like LinkedHashSet do.
Q3. Is HashSet faster than HashMap?
Not exactly. HashSet and HashMap offer similar performance for most operations because HashSet is internally backed by a HashMap. Both provide constant-time performance (O(1)) for add, remove, and contains operations on average. However, HashMap stores key-value pairs, while HashSet stores only keys, making them suitable for different use cases.
Q4. Can a HashSet have duplicates?
No, HashSet cannot have duplicates. It is designed to store only unique elements. When you try to add a duplicate element, the add() method simply returns false, and the set remains unchanged. This behavior is enforced using the equals() and hashCode() methods internally.
Q5. How do I preserve insertion order with a HashSet?
To preserve insertion order in Java, use LinkedHashSet instead of HashSet. While HashSet does not maintain any order of elements, LinkedHashSet maintains the order in which elements are inserted. It combines the hashing functionality of HashSet with a linked list to ensure predictable iteration order, making it ideal for ordered sets.