Immutable collection in java

Immutable collection in java is the most important topic and it is often asked in interview questions. In this post, we will discuss immutable classes, immutable collections and how many ways to create an immutable collection.

An object is considered immutable if its state can’t change after it is constructed. Before moving further, we should know the immutable class in java.

Here is the table content of the article will we will cover this topic.

Immutable collection in java

What is the immutable collection in java?

1. Immutable collections in java are those collections that can’t be modified once they are created.  An instance/object of immutable collection holds the same data as long as a reference to it exists, you can’t modify it after creation.

2. Immutable collections are thread-safe because any thread can’t modify it. So multiple threads can read it but can’t modify it because the structure doesn’t support the mutation.  

3. Immutable collections are more space-efficient generally the instance of an immutable collection consumes much less memory than their mutable collection.

Immutable collection before Java 9

Before Java 9, we didn’t use the actual term of immutable collection. You must think why I am saying this statement but yes, it’s true. Let me try to make understand.

In collections class, we have some methods those returns the unmodified collections. These methods don’t return any immutable object of collection. They just return an unmodified view or read-only view of the original collection. So that we can’t perform any modification operations like add, remove, replace, clear, etc through the references returned by these wrapper methods.  Before java 9, we used these methods for a multi-threading environment.

But it’s not an actual concept of immutable because we can modify the original collection and those modifications will be reflected in the view returned by these methods.

Let’s take the example of an unmodified collection and see how it works. Here we are creating a list of sports (listOfSports) and then getting an unmodifiable list (unModifiableListOfSports) through Collections class. The Collections.unmodifiableList() returns read-only view of the original listOfSports. We are adding the elements in the original listOfSports and those elements will be reflected in unModifiableListOfSports. Let’s see the immutable list in java

But we can’t add elements to unModifiableListOfSports. When we tried to add, it gave a UnsupportedOperationException.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ImmutableCollections
{
    public static void main(String[] args)
    {
        List<String> listOfSports = new ArrayList<String>();

        listOfSports.add("Hockey");
        listOfSports.add("Cricket");
        listOfSports.add("Tennis");
        //Output : [Hockey, Cricket, Tennis]
        System.out.println("Before modification: " +listOfSports);

        List<String> unModifiableListOfSports = Collections.unmodifiableList(listOfSports);
        //Output : [Hockey, Cricket, Tennis]
        System.out.println("Before modification: " +unModifiableListOfSports);

        //Adding a new sport in list and will be reflected in unModifiableListOfSports
        listOfSports.add("Kabaddi");

        System.out.println("After modification: " +listOfSports);    //Output : [Hockey, Cricket, Tennis, Kabaddi]

        System.out.println("After modification: " +unModifiableListOfSports);    //Output : [Hockey, Cricket, Tennis, Kabaddi]

        //It gives run-time error because modifications are not allowed
        unModifiableListOfSports.add("Wrestling");
    }
}

Output: Before modification: [Hockey, Cricket, Tennis]
Before modification: [Hockey, Cricket, Tennis]
After modification: [Hockey, Cricket, Tennis, Kabaddi]
After modification: [Hockey, Cricket, Tennis, Kabaddi]
Exception in thread “main” java.lang.UnsupportedOperationException  at java.base/java.util.Collections$UnmodifiableCollection.add(Collections.java:1067) at ImmutableCollections.main(ImmutableCollections.java:29)

Collections class has some other wrapper methods to create unmodifiable collections to create an unmodifiable set we use Collections.unmodifiableSet and Collections.unmodifiableMap to create the unmodifiable map.

Immutable and Unmodifiable Are Not the Same

Let’s discuss an important interview question about immutable and unmodifiable.

Question: What is the difference between immutable and unmodifiable? OR
Are immutable collections and unmodifiable collections the same?

Answer: No, these are not the same. We prepare some differences to let’s read them one by one.  

  1. An immutable collection can’t be modified if we try to modify it, it throws exceptions and it’s truly immutable. But an unmodifiable collection can be modified using the original collection.
  2. The immutable collections are not wrappers because these are data structures implemented by classes. But the Collections.unmodifiableXXX method, just returns an unmodifiable view of the original collection.

Let’s take the example of immutable collection and unmodifiable collection. Here we will take an example of immutable HashSet and unmodifiable HashSet.

Immutable collection in java 9

Java 9 introduced some factory methods those are used to create an immutable collection in java. These methods are useful when we want to create the Immutable List, Set, Map, and Map.Entry. You can visit the below link and see how we can create the immutable list.

Factory method to create Immutable List
Factory method to create Immutable Set
Factory method to create Immutable Map 

Characteristics Of Java 9 Immutable Collections

  1. In immutable collections the modifications are not allowed, If you try to modify them, UnsupportedOperationException will be thrown.
  2. It doesn’t allow null elements and gives NullPointerException at run time.
  3. These immutable collections are thread-safe. You can use them in a multi-threaded environment without synchronization.

Immutable collection in java 10

Java 10 provides some new methods to create immutable collections. Now we have copyOf() method that takes an object of mutable collection and returns the immutable collections. Java 10 introduced copyOf() method in interfaces and it’s a static method. After java 10 the copyOf() method is available in List, Set, and Map.

Before moving further, we will discuss the problem that we were facing in the Factory method those were introduced in java 9.

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

public class ImmutableCollections
{
    public static void main(String[] args)
    {
        // Creating a mutable list
        List<String> listOfSports = new ArrayList<String>();
        ImmutableCollections object = new ImmutableCollections();
        // Gathering list from multiple
        listOfSports.addAll(object.sportsFromAGrade());
        listOfSports.addAll(object.sportsFromBGrade());
        listOfSports.addAll(object.sportsFromCGrade());
        
        // Creating an object of immutable collection
        List<List<String>> immutableListOfSports = List.of(listOfSports);

    }

    private List<String> sportsFromAGrade()
    {
        List<String> sportsFromAGrade = new ArrayList<String>();
        sportsFromAGrade.add("A Hockey");
        sportsFromAGrade.add("A Cricket");

        return  sportsFromAGrade;
    }

    private List<String> sportsFromBGrade()
    {
        List<String> sportsFromBGrade = new ArrayList<String>();
        sportsFromBGrade.add("B Hockey");
        sportsFromBGrade.add("B Cricket");

        return  sportsFromBGrade;
    }

    private List<String> sportsFromCGrade()
    {
        List<String> sportsFromBGrade = new ArrayList<String>();
        sportsFromBGrade.add("C Hockey");
        sportsFromBGrade.add("C Cricket");

        return  sportsFromBGrade;
    }
}

As you can see it’s not a convenient way to create an immutable collection using the List.of() method. Here we are gathering the list from multiple sources and them merging into one. Doing this would require creating an array of the right size, copying elements from the list into the array, and then calling List.of(array) to create the immutable snapshot. To resolve this problem Java 10 introduced copyOf() method.

There are corresponding static factory methods for Set and Map called Set.copyOf and Map.copyOf.

CopyOf() method in Java 10

You must have read the above scenario and why listOf() method was introduced in java 10. This method is available in List, Set and Map interface to create an immutable collection. This method returns an immutable collection. Let’s take an example and see how it works.

import java.util.*;

public class ImmutableCollections
{
    public static void main(String[] args)
    {
        // Creating an object of mutable list
        List<String> mutableListOfSports = new ArrayList<String>();
        mutableListOfSports.add("Cricket");
        mutableListOfSports.add("Hockey");

        // Creating an object of mutable Set
        Set<String> mutableSetOfData = new HashSet<String>();
        mutableSetOfData.add("JavaGoal.com");
        mutableSetOfData.add("Website");

        // Creating an object of mutable Set
        Map<Integer, String> mutableMapOfSports = new HashMap<Integer, String>();
        mutableMapOfSports.put(1, "Cricket");
        mutableMapOfSports.put(2, "Hockey");

        // Creating an immutable ArrayList from mutable ArrayList
        List<String> immutableListOfSports = List.copyOf(mutableListOfSports);
        // Creating an immutable HashSet from mutable HashSet
        Set<String> immutableSetOfData = Set.copyOf(mutableSetOfData);
        // Creating an immutable HashMap from mutable HashMap
        Map<Integer, String> immutableMapOfSports = Map.copyOf(mutableMapOfSports);

        System.out.println("Printing mutable collection before modification");
        System.out.println(mutableListOfSports);
        System.out.println(mutableSetOfData);
        System.out.println(mutableMapOfSports);

        System.out.println("Printing immutable collection before modification");
        System.out.println(immutableListOfSports);
        System.out.println(immutableSetOfData);
        System.out.println(immutableMapOfSports);

        mutableListOfSports.add("New Game");
        mutableSetOfData.add("Learning");
        mutableMapOfSports.put(3, "New Game");

        System.out.println("Printing immutable collection After modification");
        System.out.println(mutableListOfSports);
        System.out.println(mutableSetOfData);
        System.out.println(mutableMapOfSports);

        System.out.println("Printing immutable collection After modification");
        System.out.println(immutableListOfSports);
        System.out.println(immutableSetOfData);
        System.out.println(immutableMapOfSports);
    }
}

Output: Printing mutable collection before modification
[Cricket, Hockey]
[JavaGoal.com, Website]
{1=Cricket, 2=Hockey}
Printing immutable collection before modification
[Cricket, Hockey]
[Website, JavaGoal.com]
{1=Cricket, 2=Hockey}
Printing immutable collection After modification
[Cricket, Hockey, New Game]
[Learning, JavaGoal.com, Website]
{1=Cricket, 2=Hockey, 3=New Game}
Printing immutable collection After modification
[Cricket, Hockey]
[Website, JavaGoal.com]
{1=Cricket, 2=Hockey}

Case 1: If it takes an immutable object of collection then it returns the object of Unmodifiable collection. That is an immutable collection because it internally creates a new copy of the original collection. If we make any change in the original collection of immutable collections, those changes don’t reflect the immutable collection.  

Case 2: When the original collection is already immutable, then the copyOf() method doesn’t create a new copy it simply returns a reference to the original collection. Even collection-sharing same reference but still we can’t make any change because the original collection cannot be changed, there is no need to make a copy of it.

Let’s see an example where we see how it works. Here will take an example for List, Map and Set.

import java.util.*;

public class ImmutableCollections
{
    public static void main(String[] args)
    {
        // Creating an object of mutable list
        List<String> mutableListOfSports = new ArrayList<String>();
        mutableListOfSports.add("Cricket");
        mutableListOfSports.add("Hockey");

        // Creating an object of mutable Set
        Set<String> mutableSetOfData = new HashSet<String>();
        mutableSetOfData.add("JavaGoal.com");
        mutableSetOfData.add("Website");

        // Creating an object of mutable Set
        Map<Integer, String> mutableMapOfSports = new HashMap<Integer, String>();
        mutableMapOfSports.put(1, "Cricket");
        mutableMapOfSports.put(2, "Hockey");

        // Creating an immutable ArrayList from mutable ArrayList
        List<String> immutableListOfSports = List.copyOf(mutableListOfSports);
        // Creating an immutable HashSet from mutable HashSet
        Set<String> immutableSetOfData = Set.copyOf(mutableSetOfData);
        // Creating an immutable HashMap from mutable HashMap
        Map<Integer, String> immutableMapOfSports = Map.copyOf(mutableMapOfSports);

        System.out.println("are both list same: "+ (mutableListOfSports == immutableListOfSports));
        System.out.println("are both Set same: "+ (mutableSetOfData == immutableSetOfData));
        System.out.println("are both Map same: "+ (mutableMapOfSports == immutableMapOfSports));

        // Creating an immutable ArrayList from immutable ArrayList
        List<String> immutableListOfSports1 = List.copyOf(immutableListOfSports);
        // Creating an immutable HashSet from immutable HashSet
        Set<String> immutableSetOfData1 = Set.copyOf(immutableSetOfData);
        // Creating an immutable HashMap from immutable HashMap
        Map<Integer, String> immutableMapOfSports1 = Map.copyOf(immutableMapOfSports);


        System.out.println("are both list same: "+ (immutableListOfSports == immutableListOfSports1));
        System.out.println("are both Set same: "+ (immutableSetOfData == immutableSetOfData1));
        System.out.println("are both Map same: "+ (immutableMapOfSports == immutableMapOfSports1));
    }
}

Output: are both list same: false
are both Set same: false
are both Map same: false
are both list same: true
are both Set same: true
are both Map same: true

Java 10 Immutable/Unmodifiable Stream API Collectors

Java Streams were introduced in java 8 and by default, most Stream API Collectors represent mutable collection. But problem occurs when some wants immutable object of collection by use of Streams.

Approach 1 by use of List.of(): You must about the List.of() method to get immutable collection. You can try to use the List.Of() method and pass an instance of an immutable collection to the Collectors.toCollection() collector. But as this method provide immutable collection and can’t be changed after creation so it throws exception.

import java.util.*;
import java.util.stream.Collectors;

public class ImmutableCollections
{
    public static void main(String[] args)
    {
        // Creating an object of mutable list
        List<String> mutableListOfSports = new ArrayList<String>();
        mutableListOfSports.add("Cricket");
        mutableListOfSports.add("Hockey");

        List<String> immutableList = mutableListOfSports.stream().collect(Collectors.toCollection(List::of));
    }
}

Output: Exception in thread “main” java.lang.UnsupportedOperationException

Approach 2 by use collectingAndThen: By use of Collectors collectingAndThen, we can convert the list returned by toList() into an unmodifiable list. This method adepts an Collector and perform finishing transformation. It returns an immutable list as the source is not accessible outside the stream pipeline and hence it cannot be mutated.

import java.util.*;
import java.util.stream.Collectors;

public class ImmutableCollections
{
    public static void main(String[] args)
    {
        // Creating an object of mutable list
        List<Integer> mutableListOfNumber = new ArrayList<Integer>();
        mutableListOfNumber.add(1);
        mutableListOfNumber.add(2);
        System.out.println("Mutable list: "+ mutableListOfNumber);



        List<Integer> immutableList = mutableListOfNumber.stream()
                .map(Integer::valueOf)
                .collect(Collectors.collectingAndThen(Collectors.toList(),
                        Collections::unmodifiableList));

        System.out.println("Immutable list: "+immutableList);
    }
}

Output: Mutable list: [1, 2]
Immutable list: [1, 2]

Creating Immutable collections using Collectors – Java 10

Java 10 provides some utility methods in Collectors class, those methods provide the immutable collection. If you want to guarantee that the returned collection is immutable, you should use one of the toUnmodifiable- collectors. These collectors are:

   Collectors.toUnmodifiableList()
   Collectors.toUnmodifiableSet()
   Collectors.toUnmodifiableMap(keyMapper, valueMapper)     
   Collectors.toUnmodifiableMap(keyMapper, valueMapper, mergeFunction)

We will discuss and see the example of following Collectors methods:

  • toUnmodifiableList
  • toUnmodifiableSet
  • toUnmodifiableMap

Collectors.toUnmodifiableList

This method returns an object of Collector that accumulates the input elements into an unmodifiable List in encounter order. It doesn’t allow the null values. It throws NullPointerException if it is presented with a null value.

import java.util.*;
import java.util.stream.Collectors;

public class ImmutableCollections
{
    public static void main(String[] args)
    {
        // Creating an object of mutable list
        List<Integer> mutableListOfNumber = new ArrayList<Integer>();
        mutableListOfNumber.add(1);
        mutableListOfNumber.add(2);
        System.out.println("Mutable list: "+ mutableListOfNumber);

        List<Integer> immutableList = mutableListOfNumber.stream().collect(Collectors.toUnmodifiableList());

        System.out.println("Immutable list: "+ immutableList);

    }
}

Output: Mutable list: [1, 2]
Immutable list: [1, 2]

Collectors.toUnmodifiableSet()

The toUnmodifiableSet() method returns an instance/object of Collector that collects all input elements in Set. The null values are not allowed if any null value presented it throw NullPointerException.

import java.util.*;
import java.util.stream.Collectors;

public class ImmutableCollections
{
    public static void main(String[] args)
    {
        // Creating an object of mutable HashSet
        Set<Integer> mutableSetOfNumber = new HashSet<Integer>();
        mutableSetOfNumber.add(1);
        mutableSetOfNumber.add(2);
        System.out.println("Mutable Set: "+ mutableSetOfNumber);

        Set<Integer> immutableSetOfNumber = mutableSetOfNumber.stream().collect(Collectors.toUnmodifiableSet());

        System.out.println("Immutable list: "+ immutableSetOfNumber);
    }
}

Output: Mutable Set: [1, 2]
Immutable list: [1, 2]

Collectors. toUnmodifiableMap()

This method returns an object Collector that collect all input element in unmodified Map. If the mapped keys contain duplicates an IllegalStateException is thrown when the collection operation is performed.

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class ImmutableCollections
{
    public static void main(String[] args)
    {
        // Creating an object of mutable list
        List<String> mutableListOfNumber = new ArrayList<String>();
        mutableListOfNumber.add("1");
        mutableListOfNumber.add("2");
        System.out.println("Mutable list: "+ mutableListOfNumber);

        Map<String, Integer> immutableListOfNumber = mutableListOfNumber.stream()
                .collect(Collectors.toMap(Function.identity(), Integer::valueOf));

        System.out.println("Immutable Map: "+ immutableListOfNumber);

    }
}

Output: Mutable list: [1, 2]
Immutable Map: {1=1, 2=2}

But if duplicate key exists in mutable collection, then this method throws IllegalStateException exception. Let’s take an example and see how it works.

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class ImmutableCollections
{
    public static void main(String[] args)
    {
        // Creating an object of mutable list
        List<String> mutableListOfNumber = new ArrayList<String>();
        mutableListOfNumber.add("1");
        mutableListOfNumber.add("2");
        mutableListOfNumber.add("3");
        mutableListOfNumber.add("2");

        System.out.println("Mutable list: "+ mutableListOfNumber);

        Map<String, Integer> immutableListOfNumber = mutableListOfNumber.stream()
                .collect(Collectors.toMap(Function.identity(), Integer::valueOf));

        System.out.println("Immutable Map: "+ immutableListOfNumber);

    }
}

Output: Mutable list: [1, 2, 3, 2]
Exception in thread “main” java.lang.IllegalStateException: Duplicate key 2 (attempted merging values 2 and 2)

If there are duplicate keys, then we can pass a mergeFunction – else it throws an IllegalStateException.

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class ImmutableCollections
{
    public static void main(String[] args)
    {
        // Creating an object of mutable list
        List<String> mutableListOfNumber = new ArrayList<String>();
        mutableListOfNumber.add("1");
        mutableListOfNumber.add("2");
        mutableListOfNumber.add("3");
        mutableListOfNumber.add("2");

        System.out.println("Mutable list: "+ mutableListOfNumber);

        Map<String, Integer> immutableListOfNumber = mutableListOfNumber.stream()
                .collect(Collectors.toMap(Function.identity(), Integer::valueOf, (old, newVal) -> newVal));

        System.out.println("Immutable Map: "+ immutableListOfNumber);

    }
}

Output: Mutable list: [1, 2, 3, 2]
Immutable Map: {1=1, 2=2, 3=3}

Leave a Comment