Hello everyone,

In this tutorial I will discuss the Stream API in JAVA 8. Before continuing this tutorial I recommend you to read my lambda expressions tutorial. Then you can get a better idea about Stream API.

Java 8 introduced a new package called java.util.stream, which includes classes, interfaces, and enums for performing functional-style operations on elements. This is the major new feature in Java 8.The stream API simplifies code by removing for-loops and if-conditions.Streams help to perform operations on data obtained from sources like Collections, Arrays, or I/O channels. There are two types of Stream: Sequential and Parallel. Sequential operations are performed using the stream() method, while parallel operations use the parallelStream() method.

Characteristics of Java Stream.?

  • Streams don’t store data and we can't add or remove elements from them. Therefore, they are not considered data structures. They only perform operations on the data.

  • We can divide streams into two categories.They are Terminal Operations & Intermediate operations.

  • The Java Stream API allows us to create a chain of operations that are executed in a specific order, forming a pipeline. A pipeline usually has three main components. They are source,intermediate operation and terminal operation.

    1. Source : This is the source of the stream. It can be a collection, an array, an I/O channel, or any other data source.

    2. Intermediate operation : This transforms stream elements into another stream and does not produce a final result.They create a new stream for further operations. Examples include filter, map, distinct, and sorted.

    3. Terminal Operation : This is the final operation that produces a result or a side-effect. It triggers the processing of the stream and consumes its elements. Examples include forEach, collect, reduce, and count.

  • The Java Stream API enables parallel processing of large datasets without the need for writing Multithreaded code, using the concept of parallel streams.

  • We can’t use the same stream more than one time. If you try to use the same stream again and again it throws an IllegalStateException and says “stream has already been operated upon or closed”.

  • In Java Streams, elements are not populated all at once, Instead , they are lazily populated on-demand. This is because intermediate operations on a stream are not evaluated until a terminal operation is invoked.

How to create a Stream in Java.

1) Using Stream.Of() method

The Stream.of() method is a static factory method that allows us to create a stream from a sequence of elements.

public class Main {
  
    public static void main(String[] args) {
        
           // Creating a stream of elements using Stream.of()
           Stream<String> stringStream = Stream.of("apple", "banana", "orange", "grape");
    
           // Performing operations on the stream (e.g., printing each element)
           stringStream.forEach(System.out::println);
    }
}

We can use the Stream.Of() method with different types of elements, including primitive types and Objects. Here are a few examples.

// Stream of integers
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);

// Stream of doubles
Stream<Double> doubleStream = Stream.of(1.0, 2.0, 3.0, 4.0, 5.0);

// Stream of characters
Stream<Character> charStream = Stream.of('a', 'b', 'c', 'd', 'e');

// Stream of objects
Stream<Person> personStream = Stream.of(
       new Person("John", 25),
       new Person("Alice", 30),
       new Person("Bob", 28)
);

2) Using stream() & parallelStream() methods.

In Java, we can create streams using the stream() and parallelStream() methods, which are methods provided by the Collection interface. These methods allow us to convert a collection (such as a List, Set, or Map) into a stream. Here's how we can use them:

Using Stream() Method.

public class Main {

   public static void main(String[] args) {

       // Creating a list of strings
       List<String> fruits = Arrays.asList("apple", "banana", "orange", "grape", "kiwi");

       // Creating a stream using stream() method
       Stream<String> stream = fruits.stream();

       // Performing operations on the stream (e.g., printing each element)
       stream.forEach(System.out::println);
   }
}

Using parallelStream() Method

public class Main {

   public static void main(String[] args) {

       // Creating a list of strings
       List<String> fruits = Arrays.asList("apple", "banana", "orange", "grape", "kiwi");

       // Creating a parallel stream using parallelStream() method
       Stream<String> parallelStream = fruits.parallelStream();

       // Performing operations on the parallel stream (e.g., printing each element)
       parallelStream.forEach(System.out::println);
   }
}

3) Using Arrays.stream() method

In java, we can create a stream from an array using the Arrays.stream() method. This method is part of the java.util.Arrays class, and it allows us to convert an array into a stream.Here's how we can use them:

public class Main {

   public static void main(String[] args) {

       // Creating an array of integers
       int[] numbers = {1, 2, 3, 4, 5};

       // Creating a stream from the array using Arrays.stream()
       IntStream intStream = Arrays.stream(numbers);

       // Performing operations on the stream (e.g., printing each element)
       intStream.forEach(System.out::println);
   }
}

4) Using Stream.builder() method

In Java, we can create a stream using the Stream.builder() method. This method is part of the java.util.stream.Stream class and allows us to construct a stream by adding elements to it dynamically. Here's how you can use it:

public class Main {

    public static void main(String[] args) {
    
           // Creating a stream using Stream.builder()
           Stream.Builder<String> streamBuilder = Stream.builder();
        
           // Adding elements to the stream dynamically
           streamBuilder.add("apple");
           streamBuilder.add("banana");
           streamBuilder.add("orange");
    
           // Building the stream
           Stream<String> stringStream = streamBuilder.build();
        
           // Performing operations on the stream (e.g., printing each element)
           stringStream.forEach(System.out::println);
    }
}

Using Stream.builder() is particularly useful when we need to construct a stream programmatically or when we don't know the elements of the stream in advance. It provides a flexible way to build streams step by step before performing any operations on them.

5) Using Stream.empty() method.

In Java we can create an empty stream using the Stream.empty() method. This method is a static factory method provided by the java.util.stream.Stream class, and it creates an empty sequential stream. This empty stream does not contain any elements. Here’s how you can use it;

public class Main {

   public static void main(String[] args) {

       // Creating an empty stream using Stream.empty()
       Stream<String> emptyStream = Stream.empty();

       // Performing operations on the empty stream
       // For example, counting the elements (which should be 0)
       long count = emptyStream.count();

       System.out.println("Number of elements in the empty stream: " + count);
   }
}

6) Using Stream.generate() method.

In Java, we can create a stream using the Stream.generate() method from the java.util.stream.Stream class. This method allows us to generate a stream of elements using a Supplier, which is a functional interface providing a method for supplying values.

public class Main {

    public static void main(String[] args) {
        // Create a stream of random numbers using Stream.generate()
        Stream<Integer> randomStream = Stream.generate(() -> new Random().nextInt(100));

        // Limit the stream to print only first 10 random numbers
        randomStream.limit(10).forEach(System.out::println);
    }
}

Stream.generate() is useful when you want to create an infinite stream of values or when you want to generate elements on-the-fly. Keep in mind that if you're working with an infinite stream, you may need to use operations like limit() to restrict the number of elements you want to process.

7) Using Stream.iterate() method.

In Java, we can create a stream using the Stream.iterate() method from the java.util.stream.Stream class. It generates a stream of elements based on an initial seed value and a function that produces the next element from the previous one.

public class Main {

   public static void main(String[] args) {

       // Creating a stream using Stream.iterate()
       Stream<Integer> integerStream = Stream.iterate(0, n -> n + 2);

       // Limiting the stream to a certain number of elements
       // In this case, we limit it to 5 elements
       integerStream.limit(5)
               .forEach(System.out::println);
   }
}

In above example,

Stream<Integer> integerStream = Stream.iterate(0, n -> n + 2); creates a stream of integers starting from 0, where each element is produced by adding 2 to the previous one.

integerStream.limit(5) limits the stream to a specific number of elements (in this case, 5).

The terminal operation forEach(System.out::println) is used to print each element of the stream.

Stream.iterate() is useful for creating a stream with a specific iterative pattern or sequence of values.

You may need to use operations like limit() to restrict the number of elements you want to process, especially if you are working with an infinite stream.

Let's discuss How to convert Stream to Collection.

Example 1 : Convert Stream of a String to a list

We can convert a Stream of strings to a List in Java using the Collectors.toList() method. This method is part of the java.util.stream.Collectors class and is used with the collect() terminal operation to gather the elements of a stream into a List.

public class Main {

    public static void main(String[] args) {
        
           // Creating a Stream of strings
           Stream<String> stringStream = Stream.of("apple", "banana", "orange", "grape", "kiwi");
     
           // Converting the Stream to a List
           List<String> stringList = stringStream.collect(Collectors.toList());
    
           // Printing the elements of the List
           System.out.println("List of strings: " + stringList);
    }
}

Remember that toList() is just one of many collectors in the Collectors utility class. Depending on your requirements, you may select different collectors, such as toSet(), toMap(), and so on.

Example 2 : Convert Stream of a int to a list of integer

There are two ways we can do this.

1. Using the boxed method.

You can use , below code snippet to create an integer list. Here the boxed() method converts int to integer.

public class Main {
   public static void main(String[] args) {
       List<Integer> integerList = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8).boxed().collect(Collectors.toList());
   }
}

2. Using the mapped object method.

You can use , below code snippet to create an integer list. The mapToObj method from IntStream, LongStream, and Double Stream converts primitives to instances of the associated wrapper classes, just like mapToInt, mapToLong, and mapToDouble parse streams of objects into the associated primitives. The argument to mapToObj in this example uses the Integer constructor.

public class Main {
    public static void main(String[] args) {
           List<Integer> integerList = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8).mapToObj(Integer::valueOf).collect(Collectors.toList());
    }
}

Now you have a better idea about the meaning of the Stream API, how to use it, and the how to use that function using real world examples. In the next Tutorial, I will talk about Operations of Stream API (Intermediate Operation & Terminal Operation).