Streams in Java 8 Streams in Java 8

Page content

One of the major feature of Java 8 is addition of Stream. It also has introduced the functional programming in Java. We will discuss different stream operations available in Collection, Array, IntStream with examples. We will also discuss the difference between Intermediate and Terminal operations.

Stream Operations


There are mainly three parts of any stream operations:-

1. Create Stream

These are called as Stream operation, which creates a stream from a given range or collection.

List<String>.stream(), List<Object>.stream(), Arrays.stream(), IntStream.of(), IntStream.range()

2. Process the Stream

These are called as Intermediate operation, which converts a stream to another stream as a result. They can be chained together to form a pipeline of Stream operations.

filter(), map(), sorted(), distinct(), limit(), skip()

3. Consume the Stream

These are called as Terminal operation, which converts a stream to result or collection. They can not be chained together. Any Stream operation pipeline must end with terminal operation.

collect(), reduce(), min(), max(), count(), average(), sum(), anyMatch(), allMatch(), noneMatch(), findFirst(), findAny()

Intermediate vs Terminal Operations

1. Output: Output of intermediate operation is another stream whereas output of terminal operation is a collection, array or primitive.

2. Chaining: Stream operation pipeline can have as many as intermediate operators chained together but pipeline must end with terminal operator.

3. Lazy Evaluation: Intermediate operations are evaluated lazily whereas terminal operations are eager. The intermediate operations just remain as a pipeline, and executed only when the terminal operation is executed

4. Pipeline: Stream operations pipeline can have many intermediate operations but only one terminal operation.

List<String>.stream()

We will look at various stream operations pipelines. Let’s define a fruits List collection first

List<String> fruits = Arrays.asList("mango", "apple", "banana", "grapes", "orange");

We now execute various examples of streams operations pipeline

Stream Operation | Intermediate Operation | ... | ... | Intermediate Operation | Terminal Operation

Example 1. Filter elements from List
stream() | filter() | collect()

Filter out all elements except grapes from list fruits

List<String> result = fruits.stream()                   // convert list to stream
            .filter(fruit -> !"grapes".equals(fruit))   // filter out grapes
            .collect(Collectors.toList());              // collect the output and convert streams to a List

result.forEach(System.out::println);
Output
mango apple banana orange

Example 2. Change all elements in List
stream() | map() | collect()

Map all elements of List fruits to uppercase

List<String> result = fruits.stream()            // convert list to stream
            .map(fruit -> fruit.toUpperCase())   // map to uppercase
            .collect(Collectors.toList());       // collect the output and convert streams to a List

result.forEach(System.out::println);
Output
MANGO APPLE BANANA GRAPES ORANGE

Example 3. Sort all elements in List
stream() | sorted() | collect()

Sort all elements of List fruits in alphabetical order

List<String> result = fruits.stream()        // convert list to stream
            .sorted()                        // sort in alphabetical order
            .collect(Collectors.toList());   // collect the output and convert streams to a List

result.forEach(System.out::println);
Output
apple banana grapes mango orange

Example 4. Multiple intermediate operations
stream() | filter() | map() | sorted() | collect()

Multiple processing of stream of List fruits. Filter out all elements except grapes, Map them to uppercase, Sort them alphabetically.

List<String> result = fruits.stream()                   // convert list to stream
            .filter(fruit -> !"grapes".equals(fruit))   // filter out grapes
            .map(fruit -> fruit.toUpperCase())          // map to uppercase
            .sorted()                                   // sort in alphabetical order
            .collect(Collectors.toList());              // collect the output and convert streams to a List

result.forEach(System.out::println);
Output
APPLE BANANA MANGO ORANGE

Example 5. Find elements in List
stream() | filter() | findAny() | orElse()

Filter mango from the List fruits, Return mango if found or else return null

String fruit = fruits.stream()                         // convert list to stream
            .filter(fruit -> "mango".equals(fruit))    // we love mango
            .findAny()                                 // If `findAny` then return found
            .orElse(null);                             // If not found, return null

System.out.println(fruit);
Output
mango

List<Object>.stream()


Streams works with Objects Also.

Example 1. Find the 3 highest earning employees

Let’s first do it in our usual way:-

List<Employee> employees = getAllEmployees();

// Copy to new list to avoid mutating original array
List<Employee> copy = new ArrayList<>(employees);

// Sort descending
copy.sort((o1, o2) -> o2.getSalary() - o1.getSalary());

// Get first 3
for(int i=0; i<3; i++){
  Employee employee = copy.get(i);
  System.out.println(employee.getName());
}

Now use the streams to do the same:-

List<Employee> employees = getAllEmployees();
employees.stream()
         .sorted(Comparator.comparingInt(Employee::getSalary).reversed())
         .limit(3)
         .map(Employee::getName)
         .forEach(System.out::println)

Example 2. List Collectors (Terminal Operations)

Let’s look at various collectors (terminal operations) available:-

List<Employee> employees = getAllEmployees();

// to list
List<String> listOfEmps = employees.stream()
         .limit(3)
         .map(Employee::getName)
         .collect(Collectors.toList());

// to set
Set<String> setOfEmps = employees.stream()
         .limit(3)
         .map(Employee::getName)
         .collect(Collectors.toSet());

// to map
Map<String, Employee> mapOfEmps = employees.stream()
         .limit(3)
         .collect(Collectors.toMap(e -> e.name, e -> e));

// john, amy, marcy
String names = employees.stream()
         .limit(3)
         .map(Employee::getName)
         .collect(Collectors.joining(","));

// group by dept
Map<String, List<Employee>> empByDept 
          = employees.stream()
         .collect(Collectors.groupingBy(e -> e.dept));

// count employees in each dept
Map<String, Long> countByDept 
          = employees.stream()
         .collect(Collectors.groupingBy(Employee::getDept, Collectors.counting()));

Example 3. Parallel Operations

Stream operations are executed sequentially by default. You can initiate parallel stream operations by using .parallel() operation. It is recommended to user parallel operation only when List is considerably large otherwise there will be a performance hit.

// parallel stream
Map<String, List<Employee>> empMapByDept 
          = employees.stream()
          .parallel()
          .collect(Collectors.groupingBy(e -> e.dept));

IntStream.of() | .range()


Example 1. Run a for loop from 1 to n-1
int n = 6;
IntStream.range(1, n)
         .forEach(System.out::println);
Output
1 2 3 4 5

Example 2. Get min, max, avg, count, and sum of given Array

Find the minimum of given number array:-

int[] nums = {4, 1, 13, 90, 16, 2, 0};
int min = IntStream.of(nums)   //create stream
          .min()
          .getAsInt();

System.out.println("min: " + min);
Output
min: 0

getAsInt() throw exception if min cannot be found e.g. if array is empty. Alternate method is ifPresent()

IntStream.of(new int[0])
         .min()
         .ifPresent(min -> System.out.println(min));
// OR
IntStream.of(new int[0])
         .min()
         .ifPresent(System.out::println);

We changed the lambda expression min -> System.out.println(min) to double colon System.out::println in later version. Both do the same thing.

Other similar statistics operations are:-

int[] nums = {4, 1, 13, 90, 16, 2, 0};
int min    = IntStream.of(nums).min().getAsInt();
int max    = IntStream.of(nums).max().getAsInt();
double avg = IntStream.of(nums).average().getAsDouble();
long count = IntStream.of(nums).count();
long sum   = IntStream.of(nums).sum();

Alternatively you can create a stream only once do get all statistics:-

IntSummaryStatistics stats = IntStream.of(nums).summaryStatistics();
int min    = stats.getMin();
int max    = stats.getMax();
double avg = stats.getAverage();
long count = stats.getCount();
long sum   = stats.getSum();

Example 3. Find 3 distinct smallest numbers from given Array
int[] nums = {4, 1, 13, 90, 16, 2, 0};
IntStream.of(nums)
         .distinct()
         .sorted()
         .limit(3)
         .forEach(System.out::println);
Output
0 1 2

Make note that streams works on the copy of the array and do not mutate the original array.

You can change the terminal (last) operation say if we want sum of 3 distinct small numbers:-

int[] nums = {4, 1, 13, 90, 16, 2, 0};
long sum = IntStream.of(nums)
         .distinct()
         .sorted()
         .limit(3)
         .sum();

Example 4. All possible operations on IntStream
Create a Stream
IntStream.of(numbers);           // from Array
IntStream.range(1, 101);         // 1..100
IntStream.rangeClosed(1, 100);   // 1..100
IntStream.generate(supplier());  // from supplier
Process the Stream
IntStream.of(numbers).distinct();           // distinct
IntStream.of(numbers).sorted();             // sort
IntStream.of(numbers).limit(3);             // get first 3
IntStream.of(numbers).skip(3);              // skip first 3
IntStream.of(numbers).filter(n -> n%2==0);  // only even
IntStream.of(numbers).map(n -> n*2);        // double each num
IntStream.of(numbers).boxed();              // convert each num to Integer
Consume the Stream
IntStream.of(numbers).min();                // min
IntStream.of(numbers).max();                // max
IntStream.of(numbers).sum();                // sum
IntStream.of(numbers).average();            // average
IntStream.of(numbers).count();              // count

IntStream.range(1, 100).forEach(System.out::println);         // print 1 to 99
IntStream.range(1, 100).toArray();                            // collect into array
IntStream.range(1, 100).boxed().collect(Collectors.toList()); // collect into list

IntStream.of(numbers).anyMatch(n -> n%2==1);   // is any num odd
IntStream.of(numbers).allMatch(n -> n%2==1);   // are all num odd

Array.stream()


Similar to IntStream, we can create a stream from an array using Array.stream() and apply similar operations

int[] intArray = new int[]{1, 3, 5, 7, 9, 3, 5, 99};

int min    = Arrays.stream(nums).min().getAsInt();
int max    = Arrays.stream(nums).max().getAsInt();
double avg = Arrays.stream(nums).average().getAsDouble();
long count = Arrays.stream(nums).count();
long sum   = Arrays.stream(nums).sum();

Arrays.stream(intArray)
        .distinct()
        .forEach(System.out::println);