o7planning

Java Stream Tutorial

View more Tutorials:

1- Stream

Java 8 introduces a new concept called Stream. The first time you read about the Stream API, you may be confused because its name is similar to InputStream and OutputStream. But Java 8 Stream is something completely different. Stream is monad, therefore it plays an important role in bringing functional programming into Java.
Before starting this article, I recommend you to learn about functional interfaces and some common functional interfaces such as: Supplier, Consumer, Predicate. Here are my articles:
In functional programming, a monad is a structure that represents a computation that requires a sequence of steps linked together. For simplicity, see the monad example below:
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList
      .stream() // (1)  return a Stream
      .filter(s -> s.startsWith("c")) // (2)  return a new Stream
      .map(String::toUpperCase)  // (3)  return a new Stream
      .sorted()   // (4)  return a new Stream
      .forEach(System.out::println); // (5)
Output:
C1
C2
  1. Create a Stream from a List object.
  2. Create a new Stream from the previous Stream and include only elements beginning with the letter "c".
  3. Create a new Stream from previous Stream with all elements converted to uppercase.
  4. Create a new Stream from the previous Stream by sorting the elements.
  5. Print out the elements of the last Stream.
In the previous example, steps (2) to (4) are intermediate operations because they return a Stream object. So you can call another method of Stream without having to end it with a semicolon.
A terminal operation is a method that returns void or returns a different type from Stream. In the previous example, step 5 is a terminal operation because Stream.forEach method returns void.
Here are the characteristics and advantages of Java 8 Stream:
  • No storage. A Stream is not a data structure, but only a view of a data source (Which can be an array, a list or an I/O Channel,..).
  • A Stream is functional in nature. Any modifications to a Stream will not change the data sources. For example, filtering a Stream will not delete any elements, but create a new Stream that includes the filtered elements.
  • Lazy execution. Operations on a Stream will not be executed immediately. They will be executed only when users really need results.
  • Consumable. The elements of a Stream are only visited once during the life of a Stream. Once traversed, a Stream is invalidated, just like an Iterator. You have to regenerate a new Stream if you want to traverse the Stream again.
See the illustration below to understand more how Stream works.
  1. Create a Stream from a collection.
  2. Filter colors other than red.
  3. Paint pink for the triangles.
  4. Filter shapes that are not square.
  5. Calculate the total area.
Employee class participates in a few examples in this article:
Employee.java
package org.o7planning.stream.ex;

public class Employee {

    private String name;
    private float salary;
    private String gender; // "M", "F"

    public Employee(String name, float salary, String gender) {
        this.name = name;
        this.salary = salary;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public float getSalary() {
        return salary;
    }

    public String getGender() {
        return gender;
    }

    public boolean isFemale() {
        return "F".equals(this.getGender());
    }
}

2- Stream.filter(Predicate)

Returns a Stream consisting of the elements of this Stream that match the given Predicate.
Stream<T> filter(Predicate<? super T> predicate)
Example: From a list of employees (Employee), print out a list of female employees with salaries greater than 2500.
Stream_filter_ex1.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Stream_filter_ex1 {

    public static void main(String[] args) {

        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);
        
        // Employee is Female and salary > 2500
        Predicate<Employee> predicate = e -> e.isFemale() && e.getSalary() > 2500;

        employees //
             .stream() //
             .filter(predicate) //
             .forEach(e -> System.out.println(e.getName()+ " : " + e.getSalary()));
    }
}
Output:
Mary T. : 5000.0
Sophia B. : 7000.0
If a method is non-static (non-static method), has no parameters, and returns a boolean value, then its reference is considered a Predicate. (See explanation in my article about Java Predicate).
Example: Create a Predicate from a method reference:
Predicate<Employee> p = Employee::isFemale;
 
// Same as:
 
Predicate<Employee> p = employee -> employee.isFemale();
Stream_filter_ex2.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;

public class Stream_filter_ex2 {

    public static void main(String[] args) {
        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);

        employees //
             .stream() //
             .filter(Employee::isFemale) //
             .forEach(e -> System.out.println(e.getName()+ " : " + e.getSalary()));
    }
}
Output:
Sarah M. : 2000.0
Mary T. : 5000.0
Sophia B. : 7000.0

3- Stream.sorted(Comparator)

Returns a Stream consisting of the elements of this stream, sorted according to the provided Comparator.
Stream<T> sorted(Comparator<? super T> comparator)
  • TODO Link!
Example: Sorting employees in ascending salary order:
Stream_sort_ex1.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;

public class Stream_sort_ex1 {

    public static void main(String[] args) {
        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);

        employees //
             .stream() //
             .sorted (
                 (e1,e2) -> (int) (e1.getSalary() - e2.getSalary())
              ) //
             .forEach(e -> System.out.println(e.getSalary() + " : " + e.getName()));
    }
}
Output:
1500.0 : John P.
1700.0 : Charles B.
2000.0 : Sarah M.
5000.0 : Mary T.
7000.0 : Sophia B.
Example: Sort the employee by gender and salary:
Stream_sort_ex2.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;

public class Stream_sort_ex2 {

    public static void main(String[] args) {
        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);

        employees //
             .stream() //
             .sorted (
                 (e1,e2) -> {
                     int v = e1.getGender().compareTo(e2.getGender());
                     if(v == 0) {
                         v = (int) (e1.getSalary() - e2.getSalary());
                     }
                     return v;
                 }
              ) //
             .forEach(e -> System.out.println(e.getGender()+ " : "+ e.getSalary() + " : " + e.getName()));
    }
}
Output:
F : 2000.0 : Sarah M.
F : 5000.0 : Mary T.
F : 7000.0 : Sophia B.
M : 1500.0 : John P.
M : 1700.0 : Charles B.

4- Stream.map(Function)

Returns a new Stream consisting of the results of applying the given Function to the elements of this Stream.
<R> Stream<R> map(Function<? super T,? extends R> mapper)
Example: Converts a list of String(s) to uppercase.
Stream_map_ex1.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Stream_map_ex1 {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b", "c", "d", "e");
        
        List<String> newList = list //
                    .stream() // a Stream
                    .map(s -> s.toUpperCase()) // a new Stream
                    .collect(Collectors.toList()); // Stream => List

        System.out.println(list); // [a, b, c, d, e]
        System.out.println(newList); // [A, B, C, D, E]
    }
}
If a method is non-static (non-static method), no parameters, and returns a value, then its reference is considered as a Function. (See more explanation in my article about Java Function).
// Create a Function from a method reference:
Function<String, String> f1 = String::toUpperCase;
// Same as:
Function<String, String> f2 = s -> s.toUpperCase();
Stream_map_ex2.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Stream_map_ex2 {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b", "c", "d", "e");
        
        List<String> newList = list //
                    .stream() // a Stream
                    .map(String::toUpperCase) // a new Stream
                    .collect(Collectors.toList()); // Stream => List  

        System.out.println(list); // [a, b, c, d, e]
        System.out.println(newList); // [A, B, C, D, E]
    }
}
Example: Double salary for every employee in a list:
Stream_map_ex3.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;

public class Stream_map_ex3 {

    public static void main(String[] args) {

        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);
        
        employees //
             .stream() // a Stream.
             .map((e) -> new Employee(e.getName(), e.getSalary()* 2, e.getGender())) // a new Stream.
             .forEach(c -> System.out.println(c.getName()+ " : " + c.getSalary()));
    }  
}
Output:
John P. : 3000.0
Sarah M. : 4000.0
Charles B. : 3400.0
Mary T. : 10000.0
Sophia B. : 14000.0

5- Stream.flatMap(Function)

public class Stream<T> {

    <R> Stream<R> flatMap​(Function<? super T,​? extends Stream<? extends R>> mapper);
    
    // .....
}
Stream.flatMap method is quite an useful one. It has a lot to cover so I have split it up in a separate article:
  • TODO Link!

View more Tutorials:

Maybe you are interested

These are online courses outside the o7planning website that we introduced, which may include free or discounted courses.