HashSet in Java

HashSet is a class in Java that implements the Set interface and represents an unordered collection of unique elements. HashSet uses a hash table to store the elements, and the hash table uses the hash code of the elements to store and retrieve them quickly.

Important features of HashSet in Java

  1. HashSet implements the Set interface, which means that it does not allow duplicate elements.

  2. HashSet does not guarantee the order of elements, and the order may change over time as elements are added and removed.

  3. HashSet uses the hash code of an object to determine its position in the set, which allows for constant-time performance for basic operations (add, remove, contains and size) when the hash function is properly implemented and the load factor is not exceeded.

  4. The add() method of HashSet returns a boolean value indicating whether the element was added or not. It returns true if the element was added, and false if the element was already present in the set.

  5. The remove() method of HashSet removes the specified element from the set, if it is present. If the element is not present in the set, the method does nothing.

  6. The contains() method of HashSet returns a boolean value indicating whether the set contains the specified element.

  7. The clear() method of HashSet removes all elements from the set.

  8. HashSet allows null elements. You can add null elements to a HashSet and it will not throw a NullPointerException.

  9. HashSet is not synchronized, which means that if multiple threads access a HashSet concurrently, and at least one of the threads modifies the set structurally, it must be synchronized externally.

Internal working of a HashSet

HashSet in Java internally works on a principle of a Hash Table. A Hash Table is an array of buckets, where each bucket is a linked list of entries (key-value pairs). Each entry consists of a key and a value, where the key is the object that you use to retrieve the value.

When you add an element to a HashSet, it computes the hash code of the object using the hashCode() method of the object, and then uses this hash code to determine the bucket in which the object will be stored.

If there are already some elements in that bucket, then it compares the new object with each of them using the equals() method of the object. If any of them returns true, it means that the object is already present in the HashSet, and the new element is not added. Otherwise, the new element is added to the HashSet.

In Java, the default implementation of hashCode() and equals() methods are provided by the Object class, which are based on the memory address of the object. However, it is recommended to override these methods in your custom classes to provide a meaningful implementation that takes into account the state of the object.

The capacity of a HashSet is the number of buckets it has, and the load factor is the maximum ratio of elements to buckets. When the number of elements in the HashSet reaches the load factor times the capacity, the HashSet is resized and rehashed to maintain a reasonable load factor.

The internal working of HashSet allows for constant-time performance for basic operations (add, remove, contains and size) when the hash function is properly implemented and the load factor is not exceeded. However, its lack of ordering and thread-safety may be a limitation in some scenarios.

Here's an example of using a HashSet in Java

import java.util.HashSet;
import java.util.Set;
public class HashSetExample {
  public static void main(String[] args) {
    // Creating a HashSet of integers
    Set<Integer> setOfIntegers = new HashSet<>();
    // Adding elements to the HashSet
    setOfIntegers.add(10);
    setOfIntegers.add(20);
    setOfIntegers.add(30);
    setOfIntegers.add(20); // Duplicate element, not added
    // Printing the elements of the HashSet
    System.out.println("HashSet of integers: " + setOfIntegers);
    // Removing an element from the HashSet
    setOfIntegers.remove(30);
    // Checking if an element exists in the HashSet
    System.out.println(
      "Does setOfIntegers contain 20? " + setOfIntegers.contains(20)
    );
    // Clearing all elements from the HashSet
    setOfIntegers.clear();
    // Checking if the HashSet is empty
    System.out.println("Is setOfIntegers empty? " + setOfIntegers.isEmpty());
  }
}

In this example, we create a HashSet of integers using the HashSet class. We add four elements to the HashSet, but since one of them is a duplicate, it is not added. We print the elements of the HashSet using the System.out.println() method. Next, we remove an element from the HashSet using the remove() method. We check if an element exists in the HashSet using the contains() method. We clear all elements from the HashSet using the clear() method, and then we check if the HashSet is empty using the isEmpty() method.

Using a HashSet can be useful when you need to ensure that a collection of elements is unique and does not contain any duplicates. Since HashSet uses a hash table to store the elements, it provides fast access times for adding, removing, and checking if an element exists in the HashSet. However, the elements in the HashSet are not stored in any particular order.