Collecting a Stream to an Immutable Collection in Java
Last Updated :
25 Oct, 2021
Streams and Collectors were introduced in Java 8 introduced the concept of Streams. A Stream is a sequence, a sequence of objects. We generate Streams from input sources like Arrays, Lists, etc., and support aggregation operations like filter, map, limit, reduce, etc. We use Streams to pipeline data and ultimately collect it into some form of a Collector. A Collector is nothing but the container used to store the result of the Stream processing. A Collector could be a List, Map, or a Set.
An Immutable collection is a collection whose values we cannot change once created. We do need immutable Collections because many times we can have Collections of data that do not change, i.e., Lookup lists. For example, a list of months in a year or days of the week, etc. Making such lists(which have data that does not change) immutable makes them more memory and space-efficient. They are also inherently thread-safe. Immutable objects, in general, require much less memory than their mutable counterparts.
An object is considered immutable if its state cannot change after it is constructed. After you create an immutable instance of a collection, it holds the same data as long as a reference to it exists.
Methods: Depending on the Java version, multiple methods are available for creating immutable objects as per advancement in versions of java with which we can create Immutable Collections.
- Pre-Java 10
- Creating Stream and collecting it into an unmodifiable structure using the “collectingAndThen”
- Google’s Guava library
- After Java 10
- toUnmodifiableList()
- toUnmodifiableMap()
- toUnmodifiableSet()
Scenario 1: Pre-Java 10
Method 1: Creating Stream and collecting it into an unmodifiable structure. Before, Java 10 there was no direct way to create an immutable collection in Java. One method was creating a Stream and collecting it into an unmodifiable structure using the “collectingAndThen” method.
Example:
Java
// Java Program to illustrate Collecting a Stream to an
// Immutable Collection
// Pre java 10
// Using collectingAndThen method
// Importing Collections, Collectors and Stream classes
// from java.util package
import java.util.Collections;
import java.util.stream.Collectors;
import java.util.stream.Stream;
// Main class
// PreJava10ImmutableCollections
public class GFG {
// Main driver method
public static void main(String[] args)
{
// Custom inputs integer elements in List
var unmodifiableList
= Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.collect(Collectors.collectingAndThen(
Collectors.toList(),
Collections::unmodifiableList));
System.out.println(unmodifiableList);
// Operations like this will result in an exception
unmodifiableList.add(12);
}
}
Output:
Method 2: Using Google’s Guava library. For this, a pre-requisite work is required which is to include the JAR file. The JAR for the Guava library is to download the jar and add it to the build path in eclipse. The Guava Library provides the ImmutableList class. An example of the same is given below.
Example:
Java
// Java Program to illustrate Collecting a
// Stream to an Immutable Collection
// Pre java 10 Using Google’s Guava library
// Importing Guava library
import com.google.common.collect.ImmutableList;
// Importing classes from java.util package
import java.util.List;
import java.util.stream.IntStream;
// Main class
public class PreJava10ImmutableCollections {
// main driver method
public static void main(String[] args) {
// Using the Guava Libraries
List<Integer> someList
= IntStream.range(0, 15).boxed().collect(
ImmutableList.toImmutableList());
// Print and display the elements
System.out.println(someList);
}
}
Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
Note: We can also implement our own version of making an immutable list or map or set.
Scenario 2: Java version 10 and above
Java version 10 introduced functions to create Immutable collections through the Collectors class. We have 3 methods, one for List, one for Set, and one for Map.
Method 1: Using toUnmodifiableList() method. Assume we have a Stream of the first 50 even numbers and create an immutable List from it.
Example:
Java
// Java Program to illustrate Collecting a Stream to
// an Immutable Collection
// Post java 10
// using toUnmodifiableList() method
// Importing classes from java.util package
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
// Main Class
// ImmutableCollectionList
public class GFG {
// Main driver method
public static void main(String[] args)
{
// Creating Stream class object of integer type
Stream<Integer> evenNumberStream
= Stream.iterate(0, i -> i + 2).limit(50);
// Creating List class object of integer type
List<Integer> evenNumbers
= (List<Integer>)evenNumberStream.collect(
Collectors.toUnmodifiableList());
// Print all elements in the List object
System.out.println(evenNumbers);
// These will result in
// java.lang.UnsupportedOperationException
evenNumbers.add(90);
// evenNumbers.remove(1);
}
}
Output:
Method 2: Using toUnmodifiableMap() Method
Consider an example of a Books class. The Books object has two parameters namely 'Id' and 'Name'. We will generate a Stream of book Objects. Assume that we have to generate an immutable Map from this collection of Objects. To do so, we use the toUnmodifiableMap() function.
Example:
Java
// Java Program to illustrate Collecting a Stream to
// an Immutable Collection
// Post java 10
// Using toUnmodifiableMap() method
// Importing required libraries
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
// Main Class
// ImmutableCollectionMap
public class GFG {
// Main driver method
public static void main(String[] args)
{
// Creating a Map from a Stream of books by
// creating an object of List class of books type
List<Books> libInventory = new ArrayList<Books>();
// Adding elements to the above object created
// Custom input entries
libInventory.add(
new Books(1, "Pride and Prejudice"));
libInventory.add(new Books(2, "The Sign of Four"));
libInventory.add(
new Books(3, "Sense and Sensibility"));
libInventory.add(new Books(4, "Mansfield Park"));
libInventory.add(
new Books(5, "The Materese Circle"));
libInventory.add(
new Books(6, "The Hound of Baskerville"));
libInventory.add(new Books(7, "Goodnight Moon"));
libInventory.add(new Books(
8, "How many sleeps till my Birthday"));
libInventory.add(
new Books(9, "The Bourne Identity"));
libInventory.add(new Books(10, "Murder She Wrote"));
libInventory.add(new Books(
11, "The adventures of Hercule Poirot"));
libInventory.add(
new Books(12, "The song of Ice and Fire"));
// Creating a Map class object
// Declaring object of integer and string type
Map<Integer, String> unmutableInventory
= libInventory.stream().collect(
Collectors.toUnmodifiableMap(
Books::getBookNumber,
Books::getBookName));
// Print all the elements in the Map object created
// above
System.out.println(unmutableInventory);
// This will result in an Exception
unmutableInventory.put(13, "Some book");
}
}
Output:
Method 3: Using toUnmodifiableSet() method
Implementation: Creating an unmodifiable Set from a stream to do so, we use the toUnmodifiableSet() function.
Java
// Java Program to illustrate Collecting a Stream to
// an Immutable Collection
// Post java 10
// Using toUnmodifiableSet() method
// Importing required libraries
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
// Main class
// ImmutableCollectionSet
public class GFG {
// Main driver method
public static void main(String[] args)
{
// Creating Stream object of type Double
Stream<Double> randomDecimals
= Stream.generate(Math::random).limit(30);
// Now creating Set class object of type Double
Set<Double> randomSet = randomDecimals.collect(
Collectors.toUnmodifiableSet());
// Print and display elements in Set object
System.out.println(randomSet);
// This will produce an exception
randomSet.add(100.0);
}
}
Conclusion: Here we have seen how to create Immutable Collections from a Stream in Java. We also saw the different methods used to create the Collections based on the Java version being used.
Similar Reads
Using Guava's Collectors for Collecting Streams to Immutable Collections in Java Guava is a set of core libraries from Google that includes new collection types. For example, we multimap and multisets, immutable collections, a graph library, and utilities for concurrency. It is used by many companies. In this article, we are going to see how to use Guava's Collectors for Collect
3 min read
Convert an Iterable to Collection in Java Iterable and Collection have served to be of great use in Java. Iterators are used in Collection framework in Java to retrieve elements one by one and a Collection is a group of individual objects represented as a single unit. Java provides Collection Framework which defines several classes and inte
4 min read
Collection vs Collections in Java with Example Collection: Collection is a interface present in java.util package. It is used to represent a group of individual objects as a single unit. It is similar to the container in the C++ language. The collection is considered as the root interface of the collection framework. It provides several classes
3 min read
Convert an Iterable to Stream in Java Given an Iterable, the task is to convert it into Stream in Java. Examples: Input: Iterable = [1, 2, 3, 4, 5] Output: {1, 2, 3, 4, 5} Input: Iterable = ['G', 'e', 'e', 'k', 's'] Output: {'G', 'e', 'e', 'k', 's'} Approach: Get the Iterable. Convert the Iterable to Spliterator using Iterable.spliterat
1 min read
Difference Between Streams and Collections in Java Collection is an in-memory data structure, which holds all the values that the data structure currently has. Every element in the Collection has to be computed before we add it to the Collection. Operations such as searching, sorting, insertion, manipulation, and deletion can be performed on a Colle
3 min read