Thumbnail image

Improve your Java code using - Stream API - Part 1

15/1/2024 6-minute read

In this article I would like to bring you one of the major addition to Java 8, the Stream Api.

A Stream in Java can be defined as a sequence of elements from a source that supports aggregate operations on them. The source here refers to collections or arrays that provide data to a stream. There is some important things We need to note about streams:

  1. The stream itself is not a data structure, it a bunch of operations that can be applied to sources like a collections, arrays or I/O channels.
  2. The stream don’t change the original data structure.
  3. That can be zero or more intermediate operations to transform stream into another stream.
  4. Intermediate operations is lazily executed (discussed later, or in other article).
  5. The result of the stream is produced by the terminal operations.

Stream creation

That can be created from different element sources like a collections and array with the help of stream() and of() methods. Let’s see the different ways to create a stream.

a) Stream.of(v1, v2, v3… .)

import java.util.stream.Stream;

public class StreamExample {
    public static void main(String[] args) {
        
        Stream<Integer> stream = Stream.of( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
        stream.forEach(n -> System.out.println(n));
        // You can replace lambda by the reference method
        stream.forEach(System.out::println)
    }
}

b) List.stream()

Now, let’s create a stream from a list.

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

public class StreamExample {
    public static void main(String[] args) {

        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        list.add("f");

        Stream<String> stream = list.stream();
        stream.forEach(System.out::println);
    }
}

The Stream interfaces

Few interfaces are defined into Stream API, such as Stream, IntStream, LongStream, etc. Important to note the Stream<T> interface is for object elements. For primitives, it defines IntStream, LongStream and DoubleStream interfaces.

It is a good practice dealing with primitives in the Stream, because wrapping primitives to objects and auto-boxing is a costly process. Below you can see the complete list of method defined in Stream API.

image

Stream methods list

Methods defined by these interfaces can be divided in two categories:

Intermediate operations

These methods do not produce any results. Normally they accept functional interfaces as a parameters and return a new stream. Examples of intermediates operations are filter(), map(), etc.

Terminal operations

Methods that produce some results, like a count(), toArray(..), and collect(..).

The streams operations can be classified by:

  1. filtering (We will see some examples in this article)
  2. slicing
  3. mapping
  4. matching and finding
  5. reduction
  6. collect

So, We already had a basic brief about the streams. In the next We will explore some operations and see how these methods are combined. Let’s start with filtering operations.

Filtering Operations in Stream

The filtering operation is a way to give a stream and returns a new stream which contains only those elements that are required for the next operation.

filter( ) method

This is the intermediate operation. The Stream interface has a filter() method to filter a stream. Let’s take a look below in the definition.

Stream filter(Predicate<? super T>predicate)

Parameter -> A predicate to apply to each element to determine if it should be included.

Return Type -> It returns a stream consisting of the elements of this stream that match the given predicate.

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

public class StreamFilter {
    public static void main(String[] args) {
        
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(25);
        list.add(13);
        list.add(43);
        list.add(15);
        list.add(6);
        list.add(70);
        list.add(28);
        list.add(29);
        list.add(11);

        list.stream()                          // Created a stream from the list
                .filter(n -> n > 10)           //filter operation to get only numbers greater than 10
                .forEach(System.out::println); // Printing each number in the list after filtering.

        System.out.println("Original list is not modified");
        list.stream()
                .forEach(System.out::println);
    }
}

Let’s describe the above example.

  1. We create a list of Integers.
  2. We create a stream of our list.
  3. We apply the filter() operation on the stream. Because We want to print only those numbers are greater than 10.
  4. After that, We print the original list to prove the stream not make any modification on that list.

filter() with custom object

Let’s look at another example of filter() with a custom object.

In the below example, we are using multiple conditions in the filter method.

import java.util.ArrayList;
import java.util.List;

public class StreamFilter {
    public static void main(String[] args) {

        List<Player> list = new ArrayList<>();
        list.add(new Player("Zidane", 17));
        list.add(new Player("Ronaldo", 25));
        list.add(new Player("Messi", 38));
        list.add(new Player("Rodrigo", 21));
        list.add(new Player("Dudu", 28));
        list.add(new Player("Coutinho", 36));
        list.add(new Player("Romario", 26));
        
        // We are filtering out those players whose age is more than 21 and less than 30
        list.stream()
                .filter(players -> players.getAge() > 21 && players.getAge() > 30)
                .forEach(System.out::println);
    }
}

public class Player {

    String name;
    int age;

    public Player(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Player { " + "name = " + name + ", age = " + age + " }";
    }
}

In the above example, we used multiple conditions inside our filter.

filter()chaining

In the last example We wrote all the filter conditions in a single filter.

But We can also chain a filter method and make our code more readable. Look this:

import java.util.ArrayList;
import java.util.List;

public class StreamFilter {
    public static void main(String[] args) {

        List<Player> list = new ArrayList<>();
        list.add(new Player("Zidane", 17));
        list.add(new Player("Ronaldo", 25));
        list.add(new Player("Messi", 38));
        list.add(new Player("Rodrigo", 21));
        list.add(new Player("Dudu", 28));
        list.add(new Player("Coutinho", 36));
        list.add(new Player("Romario", 25));

        list.stream()
               .filter(player -> player.getName() != null ) // Filtering the object where name is not null
               .filter(player -> player.getAge() > 18 ) // Filtering the objects where age is greater than 18
               .filter(player -> player.getAge() < 30) // Filtering the objects where age is less than 30
               .forEach(System.out::println);
    }
}

public class Player {

    String name;
    int age;

    public Player(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Player { " + "name = " + name + ", age = " + age + " }";
    }
}

Conclusion

in this article We had a basic introduction about Stream API, understand the difference between Intermediate Operation and Terminal Operations. Also, We saw some examples with intermediate operations. I hope you learned something new, and in the next articles I would like to show more example and deep dive into the Stream API.

Posts in this Series