Home Java HashSet in Java explained with examples

HashSet in Java explained with examples

The Set interface has its implementation handled by the HashSet class, supported by a hash table that is a HashMap instance. The iteration order of the Set is not guaranteed, which means that the class does not guarantee the same order of elements across time. The null element is allowed in this class. The class also provides constant-time performance for fundamental operations such as add, remove, contain, and size. As we’ll see later in the article, the hash function properly distributes the elements among the buckets.

HashSet in Java

HashSet has a few key characteristics:

  • Set Interface is implemented.
  • HashSet’s fundamental data structure is Hashtable.
  • Duplicate values are not permitted because it implements the Set Interface.
  • The order in which objects are placed into HashSet is not guaranteed.
  • The hash code is used to insert objects.
  • HashSet allows for NULL entries.
  • HashSet also implements Serializable and Cloneable interfaces.

HashSet extends Abstract Set<E> and implements the Set <E>, Cloneable, and Serializable interfaces, where E denotes the type of elements this Set keeps track of. LinkedHashSet is the only directly known HashSet subtype.

Iterating through HashSet takes time proportional to the sum of the HashSet instance’s size (the number of elements) plus the supporting HashMap instance’s “capacity” to maintain constant time performance (the number of buckets).

If iteration performance is crucial, it’s critical not to set the initial capacity too high (or the load factor too low).

Initial Capacity

The initial capacity refers to the number of buckets formed when a hashtable (HashSet employs a hashtable data structure internally). If the current bucket capacity is reached, the number of buckets will be automatically raised.

Load Factor

The load factor measures how full the HashSet can get before its capacity is adjusted automatically. The hash table undergoes rehashing (internal data structures are rebuilt) when the number of entries in the hash table exceeds the product of the load factor and the current capacity. When the table has 12 elements and the internal capacity is 16, and the load factor is 0.75, the number of buckets will automatically increase.

Effect on performance

The load factor and starting capacity are two major aspects that influence HashSet operations’ performance. A load factor of 0.75 delivers very effective time and space complexity performance. Suppose we increase the load factor number above that. In that case, the memory overhead will be decreased (the internal rebuilding operation will be minimized), but it will affect the Hashtable’s add and search operations. We should choose starting capacity intelligently to reduce the rehashing operation.

There will be no rehash operation if the initial capacity is more than the maximum number of entries divided by the load factor.

Note that the HashSet implementation is not synchronized in the sense that if multiple threads visit a hash set at the same time and at least one of the threads updates the Set, the Set must be synchronized externally. This is usually performed by synchronizing on a natural encapsulation object for the Set. If no such object exists, use the Collections.synchronizedSet method to “wrap” the Set. It should be done at the time of creation to avoid unintended unsynchronized access to the Set, as seen below:

Set s = Collections.synchronizedSet(new HashSet(...));

The following is the HashSet declaration syntax:

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable

With E referring to the type of elements saved in a HashSet.

A HashSet’s inner workings

The map provides internal support for all Set interface classes. Internally, HashSet stores its object using HashMap. You might be asking why we require a key-value pair to enter a value in HashMap but only one value in Has.

HashMap’s storage

The value we put in HashSet functions as a key to the map Object, and Java utilizes a constant variable to store its value. As a result, all of the values in the key-value combination will be the same.

HashSet Implementation in Java doc

private transient HashMap map;

// The first Constructor
//Internally, each constructor creates a HashMap Object.
public CodeHashSet()
{
    // Internally, a HashMap object is created.
    map = new HashMap();
}

// The second Constructor
public CodeHashSet(int initialCapacity)
{
    // Internally, a HashMap object is created.
    map = new HashMap(initialCapacity);
}

// In Map, a dummy value to correlate with an Object.
private static final Object PRESENT = new Object();

Take a look at the HashSet class’s add() method:

public boolean add(E e)
{
   return map.put(e, PRESENT) == null;
}

We can see that the HashSet class’s add() method calls the supporting HashMap object’s put() method by passing the element you specified as a key and the constant “PRESENT” as its value. In the same way, the remove() method works. It uses the Map interface’s delete method internally.

public boolean remove(Object o)
{
  return map.remove(o) == PRESENT;
}

HashSet holds not only unique Objects but also unique Collections of Objects such as ArrayList<E>, LinkedList<E>, Vector<E>, and so on.

// program for illustrating the concept of objects storage Collection in a HashSet

import java.io.*;
import java.util.*;

class Code CollectionObjectStorage {
	
	public static void main(String[] args)
	{
		// Instantiating a HashSet object from scratch.
		HashSet<ArrayList> set = new HashSet<>();

		// creation of ArrayList listOne
		ArrayList<Integer> listOne = new ArrayList<>();

		// creation of ArrayList listTwo
		ArrayList<Integer> listTwo = new ArrayList<>();

		// Add elements using add method
		listOne.add(1);
		listOne.add(2);
		listTwo.add(1);
		listTwo.add(2);
		set.add(listOne);
		set.add(listTwo);

		//To understand the internal storage of ArrayList in Set, print the set size.
		System.out.println(set.size());
	}
}

HashSet uses the hashCode() and equals() methods to check if an entry already exists before saving it. Two lists are regarded as equal in the preceding example if they have the same elements in the same order. Because the two lists are equal, they will both return the same hash when you use the hashCode() method on them. HashSet does not save duplicate things; if you give it two equal objects, it will only store the first, which is list1.

The HashSet class hierarchy in Java

The HashSet class in Java is used to create a collection that stores data in a hash table. It derives from AbstractSet and implements the Set interface. The following are the key features of the Java HashSet class:

  • HashSet uses a hashing method to store the elements.
  • Unique elements are contained in HashSet.
  • Null values are allowed in HashSet.
  • HashSet is a non-synchronized class.
  • HashSet does not maintain the insertion order.
  • Elements are placed based on their hashcode here.
  • For search operations, HashSet is the best option.
  • HashSet has a capacity of 16 by default, with a load factor of 0.75.

What is the difference between a list and a set?

A list can have duplicate elements, whereas a Set only has unique ones.

HashSet class hierarchy

HashSet is an extension of AbstractSet, which implements the Set interface. The Set interface inherits the Collection and Iterable interfaces in a hierarchical sequence.

Class declaration for HashSet

Let’s look at the java.util.HashSet class declaration.

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable

Java HashSet class constructors

To make a HashSet, we must first make an object of the HashSet class. The HashSet class contains several constructors that allow the HashSet to be created. The constructors accessible in this class are as follows.

HashSet()

It’s used to make a standard HashSet. This constructor is used to create an empty HashSet object with a capacity of 16 and a load factor of 0.75 by default. If we want to make an empty HashSet with the name hashSet, we can do so as follows:

HashSet<E> hashSet = new HashSet<E>();

HashSet(int capacity)

It’s used to set the hash set’s capacity to the specified integer value. Upon the addition of elements to the HashSet, the capacity grows automatically. This constructor creates an empty HashSet object with a specified initialCapacity when the object is created. The default loadFactor is 0.75 in this case.

HashSet<E> hashSet = new HashSet<E>(int capacity);

HashSet(int capacity, float loadFactor)

It’s used to set the hash set’s capacity to the given integer value capacity and the defined load factor. Essentially, this constructor creates an empty HashSet object with the initialCapacity and loadFactor supplied when the object is created.

HashSet<E> hashSet = new HashSet<E>(int capacity, float loadFactor);

HashSet(Collection<? extends E> c)

The collection ‘c’ members are utilized to initialize the hash set. This constructor creates a HashSet object containing all the elements from the specified collection. In a nutshell, this constructor is called whenever a conversion from a Collection object to a HashSet object is required. If we want to make a HashSet named hs, we can do it as follows:

HashSet<E> hashSet = new HashSet<E>(Collection C);

Java HashSet class methods

The Java HashSet class has the following methods:

add(E e)

If the supplied element is not already present, it is added to the Set. It returns a boolean type.

clear()

It’s used to remove all of the Set’s elements. This method has a void return type.

clone()

It is responsible for returning a shallow copy of this HashSet instance, with no cloning of the items themselves. The method clone() returns an object.

contains(Object o)

If this Set contains the specified element, it returns true. It has a boolean return type.

isEmpty()

In case elements do not exist in this Set, it returns true. IsEmpty() returns a boolean value.

iterator()

It’s used to return an iterator across this Set’s elements. Its return type is an Iterator<E>.

remove(Object o)

If the requested element is present, it is removed from the Set. It has a return type as boolean.

size()

It’s used to determine how many elements are in a set and returns an Int.

spliterator()

It’s used to make a late-binding, fail-safe Spliterator over the Set’s elements. The return type is a Spliterator<E>.

HashSet Example in Java

Let’s look at a simple HashSet example. The elements iterate in an unordered collection, as you can see.

  import java.util.*;  

    class CodeHashSet{  
     public static void main(String args[]){  

      //Creation of a HashSet and addition of elements  

        HashSet<String> hashSet=new HashSet();  
               hashSet.add("Ten");    
               hashSet.add("Eleven");    
               hashSet.add("Twelve");   
               hashSet.add("Thirteen");  
               hashSet.add("Fourteen");  
               Iterator<String> iterVar=hashSet .iterator();  
               while(iterVar .hasNext())  
               {  
               System.out.println(iterVar .next());  
               }  
     }  
    }  

HashSet’s methods

The HashSet class contains several methods used to manipulate the Set.

Add Elements to the HashSet

  • add() – adds the supplied element to the collection.
  • addAll() adds all the elements from the given collection to the Set.

For instance,

import java.util.HashSet;

class CodeHashset {
    public static void main(String[] args) {
        HashSet<Integer> numHashet = new HashSet<>();

        // Using add() method
        numHashet.add(2);
        numHashet.add(4);
        numHashet.add(6);
        System.out.println("HashSet: " + numHashet);

        HashSet<Integer> numVarsHashSet = new HashSet<>();
        
        // Using addAll() method
        numVarsHashSet.addAll(evenNumber);
        numVarsHashSet.add(5);
        System.out.println("New HashSet: " + numVarsHashSet);
    }
}

Accessing elements from HashSet

The iterator() method is used to access the elements of a hash set. We must first import the java.util.Iterator package before we can utilize this method. For instance,

import java.util.HashSet;
import java.util.Iterator;

class CodeHashSet {
    public static void main(String[] args) {

        HashSet<Integer> numHashSet = new HashSet<>();
        numHashSet.add(2);
        numHashSet.add(5);
        numHashSet.add(6);
        System.out.println("HashSet: " + numHashSet);

        // Calling the method iterator()
        Iterator<Integer> iterVar= numHashSet.iterator();
        System.out.print("HashSet usingthe  Iterator: ");

        // Accessing HashSet elements
        while(iterVar.hasNext()) {
            System.out.print(iterVar.next());
            System.out.print(", ");
        }
    }
}

Removal of Elements

There are two methods for removing elements, as shown below:

  • remove()- Removes the requested element from the set with remove().
  • RemoveAll()- Removes all elements from the set with removeAll().

For instance,

import java.util.HashSet;

class CodeHashSet {
    public static void main(String[] args) {
        HashSet<Integer> numHashSet = new HashSet<>();
        numHashSet.add(12);
        numHashSet.add(15);
        numHashSet.add(16);
        System.out.println("HashSet: " + numHashSet);

        // Using remove() method
        boolean valOne = numHashSet.remove(15);
        System.out.println("Confirm if 15 is removed successfully? " + valOne);

        boolean valTwo = numHashSet.removeAll(numbers);
        System.out.println("Check if all elements have been removed? " + valTwo);
    }
}

Operations in a Set

The HashSet class’s numerous methods are used to conduct various set operations.

Combination of Sets

The addAll() method can perform a union between two sets. For instance,

import java.util.HashSet;

class CodeSet {
    public static void main(String[] args) {

        HashSet<Integer> hashSet = new HashSet<>();
        hashSet.add(8);
        hashSet.add(16);
        System.out.println("The first HashSet: " + hashSet);

        HashSet<Integer> hashSetTwo = new HashSet<>();
        hashSetTwo.add(1);
        hashSetTwo.add(3);
        System.out.println("The second HashSet: " + hashSetTwo);

        //  illustration of two set Union
        hashSetTwo.addAll(hashSet);
        System.out.println("The Set's Union is: " + hashSetTwo);
    }
}

Sets that intersect

The retainAll() method can accomplish the intersection of two sets. For instance,

import java.util.HashSet;

class CodeSet {
    public static void main(String[] args) {
        HashSet<Integer> hashSet = new HashSet<>();
        hashSet.add(2);
        hashSet.add(3);
        System.out.println("The first HashSet: " + hashSet);

        HashSet<Integer> hashSetTwo = new HashSet<>();
        hashSetTwo.add(7);
        hashSetTwo.add(9);
        System.out.println("The second HashSet: " + hashSetTwo);

        //Here is the set's Intersection
        hashSetTwo.retainAll(hashSet);
        System.out.println("The Set's Intersection is: " + hashSet);
    }
}

Difference Between Sets

The removeAll() method can calculate the difference between the two sets. For instance,

import java.util.HashSet;

class CodeSet {

    public static void main(String[] args) {

        HashSet<Integer> hashSetOne = new HashSet<>();
        hashSetOne.add(12);
        hashSetOne.add(13);
        hashSetOne.add(15);
        System.out.println(" The first HashSet is: " + hashSetOne);

        HashSet<Integer> hashSetTwo = new HashSet<>();
        hashSetTwo.add(11);
        hashSetTwo.add(13);
        hashSetTwo.add(15);
        System.out.println("The second HashSet is: " + hashSetTwo);

        // The Difference existing between the first HashSet and the second  HashSet is
        hashSetOne.removeAll(hashSetTwo);
        System.out.println("The set's difference is : " + hashSetOne);
    }
}

Subset

The containsAll() method can determine whether a set is a subset of another set. For instance,

import java.util.HashSet;

class CodeSubSet {

    public static void main(String[] args) {

        HashSet<Integer> hashSetOne = new HashSet<>();
        hashSetOne.add(11);
        hashSetOne.add(12);
        hashSetOne.add(13);
        hashSetOne.add(14);
        System.out.println("The first HashSet is: " + hashSetOne);

        HashSet<Integer> hashSetTwo = new HashSet<>();
        hashSetTwo.add(12);
        hashSetTwo.add(13);
        System.out.println("The second HashSet is: " + hashSetTwo);

        // Check if hashSetTwo is a subset of  hashSetOne
        boolean varResult= hashSetOne.containsAll(primeNumbers);
        System.out.println("Is the second HashSet a subset of the first HashSet? " + varResult);
    }
}

Example of a Java HashSet that ignores duplicate items

We can see that HashSet does not allow duplicate elements in this example.

 import java.util.*;
  
    class CodeHashSet{  

     public static void main(String args[]){  

      //Creation of employees HashSet and add elements  
      HashSet<String> empHashSet=new HashSet<String>();  
      empHashSet.add("Joy");  
      empHashSet.add("Bright");  
      empHashSet.add("Arnold");  
      empHashSet.add("Emmanuel");  

      //Element Traversal
      Iterator<String> itrVar=empHashSet .iterator();  
      while(itrVar .hasNext()){  
       System.out.println(itrVar .next());  
      }  
     }  
    }  

Example of removing members from a Java HashSet

We may see various methods for removing an element here.

 import java.util.*;  
    class CodeHashSet{  

     public static void main(String args[]){  

      HashSet<String> empHashSet=new HashSet<String>();  
               empHashSet.add("Green");  
               empHashSet.add("White");  
               empHashSet.add("Ken");  
               empHashSet.add("Bright");  

               System.out.println("An initial list of elements: "+empHashSet);  

               //Removing specific element from empHashSet  
               empHashSet.remove("Bright");  

               System.out.println("After invoking remove(object) method: "+empHashSet);  

               HashSet<String> newSetOne=new HashSet<String>();  
               newSetOne.add("Ajay");  
               newSetOne.add("Gaurav");  
               empHashSet.addAll(newSetOne);  

               System.out.println("Updated Employee List: "+empHashSet);  

               //Removal of all the new elements from the HashSet  
               empHashSet.removeAll(newSetOne);  

               System.out.println("Current HashSet status after invoking the removeAll() method: "+empHashSet);  

               //Removal of  elements that meet a specified condition  
               empHashSet.removeIf(str->str.contains("White"));    
               System.out.println("After invoking removeIf() method: "+empHashSet);  

               //Removal of all the elements available in the set  
               empHashSet.clear();  
               System.out.println("After invoking clear() method: "+empHashSet);  
     }  
    }  

Another Collection’s Java HashSet

 import java.util.*;
 
    class CodeHashSet{  

     public static void main(String args[]){
 
       ArrayList<String> namesArrayList=new ArrayList<String>();  
               namesArrayList.add("Joy");  
               namesArrayList.add("Green");  
               namesArrayList.add("Bright");  
                 
               HashSet<String> namesHashSet=new HashSet(list);  
               namesHashSet.add("White");

               Iterator<String> iVar=set.iterator();  
               while(iVar .hasNext())  
               {  
               System.out.println(iVar .next());  
               }  
     }  
    }  

Example of a Java HashSet: Laptop

Let’s look at a HashSet example where we add laptops to the collection and then print them all.

  import java.util.*;  

    class Laptop {  
    int id;  
    String name,model,manufacturer;  
    int quantity;  
    public Laptop(int id, String name, String model, String manufacturer, int quantity) {  
        this.id = id;  
        this.name = name;  
        this.model = model;  
        this.manufacturer = manufacturer;  
        this.quantity = quantity;  
    }  
    }  
    public class CodeHashSet {  

    public static void main(String[] args) {  
        HashSet<Laptop> set=new HashSet<Laptop>();  
        
//Creating Laptops  
        Laptop laptopOne=new Laptop(1,"Folio Elitebook","9470M","HP",20);  
        Laptop laptopTwo=new Laptop(2,"Probook","4670W","HP",13);  
        Laptop laptopThree=new Laptop(3," Dell Omicrom","1167T","DELL",25);  
        Laptop laptopFour=new Laptop(3," Lenovo Yoga","33XTG","Lenovo",10);  
       Laptop laptopFive=new Laptop(3," Mac Slim","XX98JT2","Apple",1);  
        
      //Adding Laptop to HashSet  
        set.add(laptopOne);  
        set.add(laptopTwo);  
        set.add(laptopThree);
       set.add(laptopFour);   
       set.add(laptopFive);  

        //Traversing the Laptop HashSet  
        for( Laptop lapVar:set){  
        System.out.println(lapVar.id+" "+lapVar.name+" "+lapVar.model+" "+lapVar.manufacturer+" "+lapVar.quantity);  
        }  
    }  
    }  

Example: a program for demonstrating how the HashSet Class works

// Importing required classes
import java.util.*;

// This is the Main class

class Codeunderscored {

	// Code main's driver method
	public static void main(String[] args)
	{

		// Creating a HashSet that is empty
		HashSet<String> hashSet = new HashSet<String>();

		// Using the add() method to add elements to a HashSet
		hashSet.add("IBM");
		hashSet.add("DELL");
		hashSet.add("Chrome Book");

		// duplication of elements
		hashSet.add("DELL");

		// The HashSet is displayed.
		System.out.println(hashSet);
		System.out.println("List contains DELL or not:"
						+ hashSet.contains("DELL"));

		// Using the remove() method to remove objects from a HashSet
		h.remove("Australia");
		System.out.println("List after removing IBM:"
						+ hashSet);

		// Show a message
		System.out.println("Iterating over list:");

		// Iterating over elements in the hashSet
		Iterator<String> iterVar = hashSet.iterator();

		// This holds true until only one ingredient remains.
		while (iterVar.hasNext())

			// Using the next() method to iterate over items
			System.out.println(iterVar.next());
	}
}

Example: Program illustrating the Iteration Over HashSet

// First, Import the needed classes
import java.io.*;
import java.util.*;

//  IterateTheHashSet in the main class.
class Codeunderscored {

	// The primary driver method
	public static void main(String[] args)
	{

		// Creating a string HashSet with no entries
		HashSet<String> hashSet = new HashSet<String>();

		// Adding to the preceding Set with the add() function.
		hashSet.add("Geek");
		hashSet.add("For");
		hashSet.add("Geeks");
		hashSet.add("A");
		hashSet.add("B");
		hashSet.add("Z");

		// Using iterators to iterate across the HashSet
		Iterator itrVar = hashSet.iterator();

		// Holds true until there is only one element left in Set.
		while (itrVar.hasNext())

			// Traversing and printing elements 	
			System.out.print(itrVar.next() + ", ");
		System.out.println();

		// For traversal, use improved for loop.
		for (String strVar : hashSet)

			// Traversing and printing elements
			System.out.print(strVar + ", ");
		System.out.println();
	}
}

HashSet operations’ time complexity

The underlying data structure is a hashtable in HashSet. As a result, the amortized (average or typical case) time complexity of the hSet add, delete, and look-up (contains method) operations are O(1).

HashMap vs. HashSet

Duplicate values are not permitted in HashSet, while HashMap stores key-value pairs, and duplicate keys are not allowed. If a key is duplicated, the new value replaces the old key.

Whereas the HashSet implements the Set interface, the HashMap, on the other hand, implements the Map interface.

HashSet requires one object to be added (Object o). But to add an element to a HashMap object, you’ll need two objects: put(K key, V value).

Internally, HashSet uses HashMap to add entries. The key K in a HashSet is the argument supplied in the add(Object) method. Java assigns a dummy value for each value provided in the add(Object) method. However, dummy values are not supported by HashMap.

To store or add objects, HashSet employs the HashMap object internally. But, HashMap uses hashing to keep and add things internally.

HashSet is more time-consuming than HashMap.

HashSet uses the add() method to add or save data. On the other hand, the put() method is used by HashMap to store data.

HashSet refers to a set, for instance {11, 12, 13, 14, 15, 16, 17}. Contrary, a HashMap is a key -> value pair(key to value) map, for example {c -> 67, o -> 98, d -> 23, e -> 31}.

Comparing a HashSet and a TreeSet

Search, insert, and remove actions; these operations take the same average time. However, the TreeSet is slower than HashSet. A hash table is used to implement HashSet. For search, insert, and delete, TreeSet takes O(Log n), which is more than HashSet. However, TreeSet maintains the data ordered. Higher() (Returns the least higher element), floor(), ceiling(), and other operations are also supported. In TreeSet, these operations are likewise O(Log n), and HashSet does not implement them. A Self-Balancing Binary Search Tree is responsible for implementing TreeSet (Red-Black Tree). In Java, TreeSet is backed by TreeMap.

HashSet elements are not sorted. In Java, the TreeSet class keeps objects in a Sorted order defined by the Comparable or Comparator methods. By default, TreeSet elements are sorted ascending.

It has a number of methods for dealing with ordered sets, including first(), last(), headSet(), tailSet(), and so on.

The null object is allowed in HashSet. TreeSet does not allow null objects and throws a NullPointerException because it compares keys using the compareTo() function, which throws java.lang.NullPointerException.

HashSet compares two objects in a Set and detects duplicates using the equals() method. For the same purpose, TreeSet employs the compareTo() method. If equals() and compareTo() are not consistent, i.e., equals should return true for two equal objects. In contrast, compareTo() should return zero, and the Set interface’s contract will be broken, allowing duplicates in Set implementations like TreeSet.

Conclusion

The Java HashSet class has been covered in this session. With the help of examples, we have learned about various hash set methods and operations. The Java Collections framework’s HashSet class implements the hash table data structure’s functionality. In addition, the Set interface is implemented.

When we need to access elements randomly in Java, we utilize HashSet. It’s because hash codes are used to access elements in a hash table. An element’s hashcode is a unique identifier that aids in locating the element in a hash table. HashSet elements cannot be duplicated. As a result, each hash set element has its hashcode.

There is no synchronization in HashSet. That is, if several threads visit the hash set simultaneously, one of them updates it. Then it would help if you synchronized it from the outside.

You may also like

Leave a Comment