Sequential Data Processing With Streams

Sequential Data Processing With Streams

We'll be introducing the Stream API which is an exciting feature that represents a new way of processing data in Java 8. We'll also talk about Iterator<E> Interface as where as Collections.

TIP: For those who would like to follow along with this article, just clone my repository git clone https://github.com/steven7mwesigwa/java-tutorials.git and navigate to java-tutorials/functional-programming-with-streams/sequential-data-processing-with-streams/. In there, you should be able to play around with all the source code used in this article.

# Comparing Streams, Collections, and Iterators

Streams represent a new powerful framework for sequential data processing.

# Streams

A Stream can be simply defined as a sequence of objects supporting a special type of iteration called internal iteration.

Before we talk about internal iteration lets remind ourselves the main features of other standard representations for sequences of objects.

  1. Lists
    • For Lists, we can :-
    • add objects.
    • remove objects.
    • search objects.
    • scan/iterate objects.
  2. Iterators
    • For Iterators, we can :-
    • scan/iterate objects.
    • remove the last object that was returned.
  3. Streams
    • From an abstract point of view, Streams only have one special operation called internal iteration.
    • Internal iteration simply means apply some operation on every object of the sequence.

# Example 1: Print A Sequence Of Objects. (Using Lists)

Now, suppose our sequence is a List<E>.

Say we have a List of Student names, and our task is to print them out on the console(terminal).

Normally, we would define the list of Student names first, and then write an enhanced for statement to loop through the List.

Let's create a simple MyList.java class to use for demonstration purposes.


//MyList.java			
package com.stevenmwesigwa.sequential.data.processing;

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

public class MyList {

    public static void main(String[] args) {
		
        final List<String> studentNames = Arrays.asList("Jimmie Foxx", "Samuel L. Jackson", "Sandra Bullock", "Keanu Reeves");
				
        for (String studentName : studentNames) {
            System.out.println(studentName);
        }
    }

}

Our code above would output:-


...
--- exec-maven-plugin:1.5.0:exec (default-cli) @ sequential-data-processing-with-streams ---
Jimmie Foxx
Samuel L. Jackson
Sandra Bullock
Keanu Reeves
...

# Example 2: Print A Sequence Of Objects. (Using Iterators)

Now, suppose our sequence is an Iterator<E>.

Then here, we could use a while loop. to print the studentNames on the console(terminal).

Lets create a simple MyIterator.java class to use for demonstration purposes.


//MyIterator.java			
package com.stevenmwesigwa.sequential.data.processing;

import java.util.Arrays;
import java.util.Iterator;

public class MyIterator {

    public static void main(String[] args) {
        
        Iterator<String> studentNames = Arrays.asList("Jimmie Foxx", "Samuel L. Jackson", "Sandra Bullock", "Keanu Reeves").iterator();
        
        while (studentNames.hasNext()) {
            System.out.println(studentNames.next());
        }
    }

}

Our code above would give us the same results as in Example 1:


# Example 3: Print A Sequence Of Objects. (Using Streams)

Now, suppose our sequence is a Stream<E>.

We could then use a forEach(Consumer<? super T> action) special method. that takes a Consumer<T> as a parameter to be able to perform an action for each element of this stream.

Behind the scenes, the internal iterator forEach() method invokes the accept(T t) method of the Consumer you provide for each element. Here, we simply use method references and print out each student name.

We're basically passing a reference to the println() method of the System.out object.

Lets create a simple MyStream.java class to use for demonstration purposes.


//MyStream.java			
package com.stevenmwesigwa.sequential.data.processing;

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

public class MyStream {
    
    public static void main(String[] args) {
        
        Stream<String> studentNames = Stream.of("Jimmie Foxx", "Samuel L. Jackson", "Sandra Bullock", "Keanu Reeves");
        
        studentNames.forEach(System.out::println);
    }
    
}

As you can see, the functional style version of internal iteration is more elegant.

Here we focus on the what? and not the how?. We delegate the hows to the underlying stream library.

The underlying stream library takes care of the iteration. We only concentrate on what we want to do for each element and not how to iterate.

The java.util.stream.Stream<T> Interface comes with very many awesome methods that allow you to aggregate data in a seamless manner.

Some of the commonly used methods with Streams are:-

  • map() - Returns a stream consisting of the results of applying the given function to the elements of this stream.
  • of() - Returns a sequential ordered stream whose elements are the specified values.
  • filter() - Returns a stream consisting of the elements of this stream that match the given predicate.
  • reduce() - Performs a reduction on the elements of this stream, using an associative accumulation function, and returns an Optional describing the reduced value, if any.
  • etc...

# Types Of Operations That Can Be Applied To The Elements Of A Stream


  1. Build Operations
    • This is an operation that creates a stream from a particular data source.
  2. Intermediate Operations
    • This is an operation that converts one stream into another. i.e Into a stream of another type.
  3. Terminal Operations
    • This is an operation that converts one stream into something else (or nothing i.e. returns void).

# The Pipeline Of Stream Operations

Diagram For The Pipeline Of Stream Operations

As you can see in the diagram above, we typically start with a build operation. Then, we continue with zero or more intermediate operations. Lastly, we end with exactly one terminal operation.

NOTE: This terminal operation may or may not return a result.

# Example Demonstrating The Pipeline of Stream Operations

Let's assume that we've been given a task to list alphabetically the names of Employees with salaries greater than or equal to $3800.

Let's create a simple Employee.java class to use for demonstration purposes.


//Employee.java			
package com.stevenmwesigwa.sequential.data.processing;

import java.math.BigDecimal;

public class Employee {

    private String firstname;
    private BigDecimal salary;

    public Employee(String firstname, BigDecimal salary) {
        this.firstname = firstname;
        this.salary = salary;
    }

    public String getFirstname() {
        return firstname;
    }

    public BigDecimal getSalary() {
        return salary;
    }
}

Lets create another simple StreamOpsPipelineExamp.java class to hold our solution for the task.


//StreamOpsPipelineExamp.java		
package com.stevenmwesigwa.sequential.data.processing;

import java.math.BigDecimal;
import java.util.stream.Stream;

public class StreamOpsPipelineExamp {

    public static void main(String[] args) {
        Stream<Employee> employees = Stream.of(new Employee("Juliet", new BigDecimal("5400")), new Employee("Kardashian", new BigDecimal("3800")), new Employee("Jessica", new BigDecimal("1200")), new Employee("Austin", new BigDecimal("10400")));

        employees.filter(e -> e.getSalary().compareTo(new BigDecimal("3800")) == 0 || e.getSalary().compareTo(new BigDecimal("3800")) == 1)
                .map(Employee::getFirstname)
                .sorted()
                .forEach(System.out::println);
    }
}

Our code above would output:-


...
--- exec-maven-plugin:1.5.0:exec (default-cli) @ sequential-data-processing-with-streams ---
Austin
Juliet
Kardashian
...

Explanation:

  1. So, we basically create a Stream of Employees.
  2. We then use the filter() method to retain only Employees with salaries greater than or equal to $3800.
  3. We then use the map() method to extract the first names of the selected Employees.
  4. We then use the sorted() method to sort the first names of the selected Employees.
  5. Lastly, we then use the forEach() method that we have already seen to print out the first names of the selected Employees.

# Diagram To Demonstrate The Pipeline of Stream Operations

Diagram To Demonstrate The Pipeline Of Stream Operations With Code

As you can see, of() is a build operation. filter(), map(), sorted() are intermediate operations. forEach() is a terminal operation.

NOTE: Here, forEach() method returns nothing. i.e void but as a side effect it prints the first names of our selected Employees.

# Types Of Streams

  1. Ordered or Unordered Streams.
  2. Sequential or Parallel Streams.

# Ordered or Unordered Streams.

Ordered Streams

The objects in the Stream basically come in a fixed order. This is also called the encounter order. This is so because it is the order in which the objects will be encountered along the Stream.

Unordered Streams

The operations you perform on an Unordered Stream are going to be executed in any order.

So there is no specific guarantee on the order in which they are going to be executed.

# Sequential or Parallel Streams.

Sequential Streams

Here the operations are going to be performed on one object at a time.

Parallel Streams

Here, the operations may be performed on several objects in parallel.

Remember, for those who would like to follow along with this article, you should find the source code or code snippets on Github.

I hope you learn a thing or two from this article. Thanks for checking it out.

If anything is unclear or you wish to make any corrections, don't hesitate to leave a comment. Your feedback is greatly appreciated.

Sharing is caring. Share this article to help out others getting started with the Stream API introduced in Java 8.

Ich wünsche ihnen einen wunderbaren Tag 😉!

About The Author   

Steven Mwesigwa

Software Engineer at Vogue Book Consultancy Services Ltd


Picture Of Steven Mwesigwa

Steve is currently a software developer at Vogue Book Consultancy Services Ltd and a technology author. He holds a Dip. in civil engineering from Kyambogo University. He founded and maintains stevenmwesigwa.com a website that receives more than 1.5 thousand visits per month. Steve can be reached on Twitter at @steven7mwwesigwa