First of all, Java 8 Streams should not be confused with Java I/O streams (ex: FileInputStream etc); these have very little to do with each other.

Simply put, streams are wrappers around a data source, allowing us to operate with that data source and making bulk processing convenient and fast.

Stream API is used to process collections of objects. A stream is a sequence of objects that supports various methods which can be pipelined to produce the desired result.

The features of Java stream are –

  • A stream is not a data structure instead it takes input from the Collections, Arrays or I/O channels.
  • Streams don’t change the original data structure, they only provide the result as per the pipelined methods.
  • Each intermediate operation is lazily executed and returns a stream as a result, hence various intermediate operations can be pipelined. Terminal operations mark the end of the stream and return the result.

Before we look into Java Stream API Examples, let’s see why it was required. Suppose we want to iterate over a list of integers and find the sum of all the integers greater than 10.

Prior to Java 8, the approach to do it would be:

private static int calculateSum(List<Integer> list) {

Iterator<Integer> it = list.iterator();

int sum = 0;

while (it.hasNext()) {

int num = it.next();

if (num > 10) {

sum += num;

}

}

return sum;

}

There are three major problems with the above approach:

  1. We just want to know the sum of integers but we would also have to provide how the iteration will take place, this is also called external iteration because the client program is handling the algorithm to iterate over the list.
  2. The program is sequential in nature, there is no way we can do this in parallel easily.
  3. There is a lot of code to do even a simple task.

To overcome all the above shortcomings, Java 8 Stream API was introduced. We can use Java Stream API to implement internal iteration, that is better because java framework is in control of the iteration.

Internal iteration provides several features such as sequential and parallel execution, filtering based on the given criteria, mapping etc.

Most of the Java 8 Stream API method arguments are functional interfaces, so lambda expressions work very well with them. Let’s see how we can write above logic in a single line statement using Java Streams.

private static int sumStream(List<Integer> list) {

return list.stream().filter(i -> i > 10).mapToInt(i -> i).sum();

}

Notice that the above program utilizes java framework iteration strategy, filtering and mapping methods and would increase efficiency.

Some of the commonly used functional interfaces in the Java 8 Stream API methods are

Function and BiFunction: Function represents a function that takes one type of argument and returns another type of argument. Function<T, R> is the generic form where T is the type of the input to the function and R is the type of the result of the function.

For handling primitive types, there are specific Function interfaces – ToIntFunction, ToLongFunction, ToDoubleFunction, ToIntBiFunction, ToLongBiFunction, ToDoubleBiFunction, LongToIntFunction, LongToDoubleFunction, IntToLongFunction, IntToDoubleFunction etc.

Predicate and BiPredicate: It represents a predicate against which elements of the stream are tested. This is used to filter elements from the java stream. Just like Function, there are primitive specific interfaces for int, long and double.

Consumer and BiConsumer: It represents an operation that accepts a single input argument and returns no result. It can be used to perform some action on all the elements of the java stream.

Supplier: Supplier represents an operation through which we can generate new values in the stream. Some of the methods in Stream that takes Supplier argument are:

  • public static<T> Stream<T> generate(Supplier<T> s)
  • <R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner)