Java HashSet class is a member of the Java collections framework. It implements the Set interface. HashSets are used to store a collection of unique elements.
Key Points:
- HashSet cannot contain duplicate values.
- HashSet allows a null value.
- HashSet is an unordered collection. It does not maintain the order in which the elements are inserted.
- Internally, HashSet uses a HashMap to store its elements.
- HashSet is not thread-safe. If multiple threads try to modify a HashSet simultaneously, the final outcome is non-deterministic. Concurrent access to a HashSet in a multi-threaded environment must be explicitly synchronized.
Create a HashSet and Add Elements to it
import java.util.HashSet;
public class HashSetExample {
public static void main(String[] args) {
// 1. Create an empty HashSet
HashSet<String> fruits = new HashSet<>();
// 2. Add elements to the HashSet
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
// 3. Display the elements of the HashSet
System.out.println(fruits);
}
}
Output:
[Apple, Banana, Cherry]
Explanation:
1. An empty HashSet named fruits is created to store strings.
2. Three fruit names are added to the fruits HashSet using the add method.
3. The contents of the fruits HashSet are displayed using the System.out.println method. Since HashSet does not guarantee any specific order of its elements, the order in which the fruits appear in the output might vary.
Create a HashSet from Another Collection
import java.util.ArrayList;
import java.util.HashSet;
public class HashSetFromCollection {
public static void main(String[] args) {
// 1. Create an ArrayList with some elements
ArrayList<String> fruitsList = new ArrayList<>();
fruitsList.add("Apple");
fruitsList.add("Banana");
fruitsList.add("Cherry");
// 2. Create a HashSet using the ArrayList's elements
HashSet<String> fruitsSet = new HashSet<>(fruitsList);
// 3. Add some more elements from another collection using addAll()
ArrayList<String> moreFruits = new ArrayList<>();
moreFruits.add("Durian");
moreFruits.add("Elderberry");
fruitsSet.addAll(moreFruits);
// 4. Display the HashSet elements
System.out.println(fruitsSet);
}
}
Output:
[Durian, Elderberry, Cherry, Apple, Banana]
Explanation:
1. We start by creating an ArrayList named fruitsList containing three fruit names.
2. A HashSet named fruitsSet is then created using the elements from fruitsList via the HashSet(Collection c) constructor.
3. Another collection named moreFruits is created, and then its elements are added to fruitsSet using the addAll() method.
4. Finally, we display the contents of fruitsSet. Note that the order of elements in a HashSet is not guaranteed, so the order in the output may vary.
Access Elements of a HashSet
import java.util.HashSet;
public class AccessHashSetElements {
public static void main(String[] args) {
// 1. Create a HashSet with some elements
HashSet<String> fruitsSet = new HashSet<>();
fruitsSet.add("Apple");
fruitsSet.add("Banana");
fruitsSet.add("Cherry");
// 2. Check if an element exists in the HashSet
boolean hasApple = fruitsSet.contains("Apple");
boolean hasGrape = fruitsSet.contains("Grape");
// 3. Print results of the checks
System.out.println("HashSet contains Apple: " + hasApple);
System.out.println("HashSet contains Grape: " + hasGrape);
// 4. Print all elements of the HashSet
System.out.println("\nAll fruits in the HashSet:");
for (String fruit : fruitsSet) {
System.out.println(fruit);
}
}
}
Output:
HashSet contains Apple: true HashSet contains Grape: false All fruits in the HashSet: Apple Banana Cherry
Explanation:
1. A HashSet named fruitsSet is initialized with three fruit names.
2. The contains() method of the HashSet is used to check if specific elements ("Apple" and "Grape") are present in the set.
3. The results of the checks are then printed out.
4. A for-each loop is utilized to iterate over the HashSet and print all its elements. It is important to remember that HashSet does not maintain any specific order for its elements.
Remove Elements from a HashSet
import java.util.HashSet;
import java.util.Arrays;
import java.util.List;
public class RemoveHashSetElements {
public static void main(String[] args) {
// 1. Create a HashSet with some elements
HashSet<String> fruitsSet = new HashSet<>();
fruitsSet.add("Apple");
fruitsSet.add("Banana");
fruitsSet.add("Cherry");
fruitsSet.add("Date");
fruitsSet.add("Elderberry");
// 2. Remove an element from the HashSet
fruitsSet.remove("Apple");
System.out.println("After removing Apple: " + fruitsSet);
// 3. Remove all the elements that exist in a given collection from the HashSet
List<String> toRemove = Arrays.asList("Banana", "Date");
fruitsSet.removeAll(toRemove);
System.out.println("After removing Banana and Date: " + fruitsSet);
// 4. Remove all the elements that start with the letter 'E'
fruitsSet.removeIf(fruit -> fruit.startsWith("E"));
System.out.println("After removing elements starting with 'E': " + fruitsSet);
// 5. Clear the HashSet completely
fruitsSet.clear();
System.out.println("After clearing the HashSet: " + fruitsSet);
}
}
Output:
After removing Apple: [Elderberry, Banana, Cherry, Date] After removing Banana and Date: [Elderberry, Cherry] After removing elements starting with 'E': [Cherry] After clearing the HashSet: []
Explanation:
1. A HashSet named fruitsSet is initialized with five fruit names.
2. The remove() method of the HashSet is used to delete the "Apple" element.
3. The removeAll() method is used to remove all elements present in the toRemove collection from the HashSet.
4. The removeIf() method coupled with a lambda expression is utilized to remove all elements that start with the letter 'E'.
5. The clear() method is called to remove all elements from the HashSet, leaving it empty.
Iterate over a HashSet – Different Ways
import java.util.HashSet;
import java.util.Iterator;
public class HashSetIteration {
public static void main(String[] args) {
// Initializing a HashSet with some elements
HashSet<String> fruitsSet = new HashSet<>();
fruitsSet.add("Apple");
fruitsSet.add("Banana");
fruitsSet.add("Cherry");
fruitsSet.add("Date");
fruitsSet.add("Elderberry");
// 1. Iterate over a HashSet using Java 8 forEach and lambda expression
System.out.println("Using Java 8 forEach with lambda:");
fruitsSet.forEach(fruit -> System.out.println(fruit));
// 2. Iterate over a HashSet using iterator()
System.out.println("\nUsing iterator():");
Iterator<String> iterator = fruitsSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 3. Iterate over a HashSet using iterator() and Java 8 forEachRemaining() method
System.out.println("\nUsing iterator() with forEachRemaining():");
iterator = fruitsSet.iterator();
iterator.forEachRemaining(fruit -> System.out.println(fruit));
// 4. Iterate over a HashSet using simple for-each loop
System.out.println("\nUsing simple for-each loop:");
for (String fruit : fruitsSet) {
System.out.println(fruit);
}
}
}
Output:
Using Java 8 forEach with lambda: Apple Elderberry Banana Cherry Date Using iterator(): Apple Elderberry Banana Cherry Date Using iterator() with forEachRemaining(): Apple Elderberry Banana Cherry Date Using simple for-each loop: Apple Elderberry Banana Cherry Date
Explanation:
1. The HashSet named fruitsSet is initialized with five fruit names.
2. The forEach() method of HashSet with a lambda expression is used to iterate and print each element of the set.
3. The traditional iterator() method is used in conjunction with a while-loop to go through and print each element of the set.
4. The iterator() method, coupled with the Java 8 forEachRemaining() method and a lambda expression, is used for iteration and printing.
5. A simple enhanced for-loop (for-each loop) is used to traverse and print each element of the set.
Using HashSet with User-Defined Objects
import java.util.HashSet;
// Define a simple User class with id and name fields
class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
User user = (User) obj;
return id == user.id;
}
@Override
public int hashCode() {
return id;
}
@Override
public String toString() {
return "User{" + "id=" + id + ", name='" + name + '\'' + '}';
}
public static void main(String[] args) {
HashSet<User> users = new HashSet<>();
// Add some user objects to the HashSet
users.add(new User(1, "Alice"));
users.add(new User(2, "Bob"));
users.add(new User(3, "Charlie"));
users.add(new User(1, "Alice")); // Duplicate based on id
// Print all user objects
users.forEach(System.out::println);
}
}
Output:
User{id=1, name='Alice'} User{id=3, name='Charlie'} User{id=2, name='Bob'}
Explanation:
1. The User class is defined with id and name as fields. It overrides the equals(), hashCode(), and toString() methods.
2. equals() method is overridden to define equality of User objects based on the id field.
3. hashCode() method is overridden to return the hash code based on the id field.
4. In the main method, a HashSet of User objects is created.
5. Four User objects are added to the set. However, two of them have the same id and name. The HashSet will recognize them as duplicates based on the overridden equals() and hashCode() methods.
6. Only three distinct User objects are printed, demonstrating that the HashSet has identified and removed the duplicate entry.
Synchronizing Access to a HashSet
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class SynchronizedHashSetExample {
public static void main(String[] args) {
// Creating a HashSet
HashSet<String> set = new HashSet<>();
// Adding elements to the HashSet
set.add("A");
set.add("B");
set.add("C");
// Synchronizing access to the HashSet
Set<String> synchronizedSet = Collections.synchronizedSet(set);
// Thread-safe operation on the synchronized set
synchronized (synchronizedSet) {
for (String element : synchronizedSet) {
System.out.println(element);
}
}
}
}
Output:
A B C
Explanation:
1. A standard HashSet is created and initialized with a few string elements.
2. The Collections.synchronizedSet() method is used to wrap the original HashSet into a synchronized (thread-safe) set. This is stored in synchronizedSet.
3. While the set returned by Collections.synchronizedSet() is thread-safe, its iterators are not. Therefore, when iterating over the set using iterators (directly or via a for-each loop), we must manually synchronize on the set.
4. The synchronized block (synchronized (synchronizedSet)) ensures that the entire iteration process is thread-safe.
Conclusion
In this tutorial, you learned what is a HashSet, key points about HashSet, how to create a HashSet, how to add elements to a HashSet, how to access elements from HashSet, how to remove elements from the HashSet, how to iterate over a HashSet, how to synchronize HashSet, and how to create a HashSet of user-defined objects.