Java streams

In this part of the Java tutorial, we will work with streams. Streams vastly improve the processing of data in Java.

Stream is a sequence of elements from a source that supports sequential and parallel aggregate operations. The common aggregate operations are: filter, map, reduce, find, match, and sort. The source can be a collection, IO operation, or array, which provides data to a stream.

A Java collection is an in-memory data structure with all elements contained within memory while a stream is a data structure with all elements computed on demand. In contrast to collections, which are iterated explicitly (external iteration), stream operations do the iteration behind the scenes for us. Since Java 8, Java collections have a stream() method that returns a stream from a collection.

The Stream interface is defined in java.util.stream package.

An operation on a stream produces a result without modifying its source.

Characteristics of a stream

Stream pipeline

A stream pipeline consists of a source, intermediate operations, and a terminal operation. Intermediate operations return a new modified stream; therefore, it is possible to chain multiple intermediate operations. Terminal operations, on the other hand, return void or a value. After a terminal operation it is not possible to work with the stream anymore. Short-circuiting a terminal operation means that the stream may terminate before all values are processed. This is useful if the stream is infinite.

Intermediate operations are lazy. They will not be invoked until the terminal operation is executed. This improves the performance when we are processing larger data streams.

Creating streams

Streams are created from various sources such as collections, arrays, strings, IO resources, or generators.

CreatingStreams.java
package com.zetcode;

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

public class CreatingStreams {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("pen", "coin", "desk", "chair");

        String word = words.stream().findFirst().get();
        System.out.println(word);
        
        Stream<String> letters = Arrays.stream(new String[]{ "a", "b", "c"});        
        System.out.printf("There are %d letters%n", letters.count());
        
        String day = "Sunday";
        IntStream istr = day.codePoints();
        
        String s = istr.filter(e -> e != 'n').collect(StringBuilder::new,
                StringBuilder::appendCodePoint, StringBuilder::append).toString();
        System.out.println(s);
    }
}

In this example, we work with streams created from a list, array, and a string.

List<String> words = Arrays.asList("pen", "coin", "desk", "chair");

A list of strings is created.

String word = words.stream().findFirst().get();

With the stream method, we create a stream from a list collection. On the stream, we call the findFirst() method which returns the first element of the stream. (It returns an Optional from which we fetch the value with the get() method.)

Stream<String> letters = Arrays.stream(new String[]{ "a", "b", "c"});        
System.out.printf("There are %d letters%n", letters.count());

We create a stream from an array. The count() method of the stream returns the number of elements in the stream.

String day = "Sunday";
IntStream istr = day.codePoints();

String s = istr.filter(e -> e != 'n').collect(StringBuilder::new,
        StringBuilder::appendCodePoint, StringBuilder::append).toString();
System.out.println(s);

Here we create a stream from a string. We filter the characters and build a new string from the filtered characters.

$ java com.zetcode.CreatingStreams
pen
There are 3 letters
Suday

This is the output.

There are three Stream specializations: IntStream, DoubleStream, and LongStream.

CreatingStreams2.java
package com.zetcode;

import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

public class CreatingStreams2 {

    public static void main(String[] args) {
        
        IntStream integers = IntStream.rangeClosed(1, 16);
        System.out.println(integers.average().getAsDouble());
        
        DoubleStream doubles = DoubleStream.of(2.3, 33.1, 45.3);
        doubles.forEachOrdered(e -> System.out.println(e));
        
        LongStream longs = LongStream.range(6, 25);
        System.out.println(longs.count());
    }
}

The example works with the three aforementioned specializations.

IntStream integers = IntStream.rangeClosed(1, 16);
System.out.println(integers.average().getAsDouble());

A stream of integers is created with the IntStream.rangeClosed() method. We print their average to the console.

DoubleStream doubles = DoubleStream.of(2.3, 33.1, 45.3);
doubles.forEachOrdered(e -> System.out.println(e));

A stream of double values is created with the DoubleStream.of() method. We print the ordered list of elements to the console utilizing the forEachOrdered() method.

LongStream longs = LongStream.range(6, 25);
System.out.println(longs.count());

A strem of long integers is created with the LongStream.range() method. We print the count of the elements with the count() method.

$ java com.zetcode.CreatingStreams2
8.5
2.3
33.1
45.3
19

This is the output of the example.

The Stream.of() method returns a sequential ordered stream whose elements are the specified values.

CreatingStreams3.java
package com.zetcode;

import java.util.Comparator;
import java.util.stream.Stream;

public class CreatingStreams3 {

    public static void main(String[] args) {

        Stream<String> colours = Stream.of("red", "green", "blue");
        String col = colours.skip(2).findFirst().get();    
        System.out.println(col);
        
        Stream<Integer> nums = Stream.of(3, 4, 5, 6, 7);
        int maxVal = nums.max(Comparator.naturalOrder()).get();
        System.out.println(maxVal);
    }
}

In the example, we create two streams with the Stream.of() method.

Stream<String> colours = Stream.of("red", "green", "blue");

A stream of three strings is created.

String col = colours.skip(2).findFirst().get();    

With the skip() method, we skip two elements and find the only one left with the findFirst() method.

Stream<Integer> nums = Stream.of(3, 4, 5, 6, 7);
int maxVal = nums.max(Comparator.naturalOrder()).get();

We create a stream of integers and find its maximum number.

$ java com.zetcode.CreatingStreams3
blue
7

This is the output.

Other methods to create streams are: Stream.iterate() and Stream.generate().

CreatingStreams4.java
package com.zetcode;

import java.util.Random;
import java.util.stream.Stream;

public class CreatingStreams4 {

    public static void main(String[] args) {

        Stream<Integer> s1 = Stream.iterate(5, n -> n * 2).limit(10);
        s1.forEach(System.out::println);

        Stream.generate(new Random()::nextDouble)
                .map(e -> (e * 10))
                .limit(5)
                .forEach(System.out::println);        
    }
}

In the example, we create two streams with Stream.iterate() and Stream.generate().

Stream<Integer> s1 = Stream.iterate(5, n -> n * 2).limit(10);
s1.forEach(System.out::println);

The Stream.iterate() returns an infinite sequential ordered stream produced by iterative application of a function to an initial element. The initial element is called a seed. The second element is generated by applying the function to the first element. The third element is generated by applying the function on the second element etc.

Stream.generate(new Random()::nextDouble)
        .map(e -> (e * 10))
        .limit(5)
        .forEach(System.out::println); 

A stream of five random doubles is created with the Stream.generate() method. Each of the elements is multiplied by ten. In the end, we iterate over the stream and print each element to the console.

$ java com.zetcode.CreatingStreams4
5
10
20
40
80
160
320
640
1280
2560
8.704675577530493
5.732011478196306
3.8978402578067515
3.6986033299500933
6.0976417139147205

This is the output.

It is possible to create a stream from a file.

CreatingStreams5.java
package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class CreatingStreams5 {

    public static void main(String[] args) throws IOException {

        Path path = Paths.get("/home/janbodnar/myfile.txt");
        Stream<String> stream = Files.lines(path);
        
        stream.forEach(System.out::println);
    }
}

The example reads a file and prints its contents using streams.

Path path = Paths.get("/home/janbodnar/myfile.txt");

A Path object is created with Paths.get() method. A Path object is used to locate a file in a file system.

Stream<String> stream = Files.lines(path);

From the path, we create a stream using Files.lines() method; each of the elements of the stream is a line.

stream.forEach(System.out::println);

We go through the elements of the stream and print them to the console.

Internal and external iteration

Depending on who controls the iteration process, we distinguish between external and internal iteration. External iteration, also known as active or explicit iteration, is handled by the programmer. Until Java 8, it was the only type of iteration in Java. For external iteration, we use for and while loops. Internal iteration, also called passive or implicit iteration, is controlled by the iterator itself. Internal iteration is available in Java streams.

ExternalIteration.java
package com.zetcode;

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

public class ExternalIteration {

    public static void main(String[] args) {
        
        List<String> words = Arrays.asList("pen", "coin", "desk", 
                "eye", "bottle");
        
        Iterator it = words.iterator();
        
        while (it.hasNext()) {
            
            System.out.println(it.next());
        }
    }
}

In the code example, we retrieve and iterator object from a list of strings. Using iterator's hasNext() and next() method in a while loop, we iterate over the elements of the list.

In the following example, we iterate the same list using an external iteration.

InternalIteration.java
package com.zetcode;

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

public class InternalIteration {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("pen", "coin", "desk",
                "eye", "bottle");
        
        words.stream().forEach(System.out::println);
    }
}

In the example, we create a stream from a list. We use the stream's forEach() to internally iterate over the stream elements.

Filtering

Filtering streams of data is one of the most important abilities of streams. The filter() method is an intermediate operation which returns a stream consisting of the elements of a stream that match the given predicate. A predicate is a method that returns a boolean value.

FilterStream.java
package com.zetcode;

import java.util.Arrays;
import java.util.stream.IntStream;

public class FilterStream {

    public static void main(String[] args) {
        
        IntStream nums = IntStream.rangeClosed(0, 25);
        
        int[] vals = nums.filter(e -> e > 15).toArray();
        
        System.out.println(Arrays.toString(vals));
    }
}

The code example creates a stream of integers. The stream is filtered to contain only values greater than fifteen.

IntStream nums = IntStream.rangeClosed(0, 25);

With IntStream, a stream of twenty six integers is created. The rangeClose() method creates a stream of integers from a bound of two values; these two values (start and end) are both included in the range.

int[] vals = nums.filter(e -> e > 15).toArray();

We pass a lambda expression (e -> e > 15) into the filter() function; the expression returns true for values greater than 15. The toArray() is a terminal operation that transforms a stream into an array of integers.

System.out.println(Arrays.toString(vals));

The array is printed to the console.

$ java com.zetcode.FilterStream
[16, 17, 18, 19, 20, 21, 22, 23, 24, 25]

The example produces this output.

The next example produces a list of event numbers.

FilterStream2.java
package com.zetcode;

import java.util.stream.IntStream;

public class FilterStream2 {

    public static void main(String[] args) {
        
        IntStream nums = IntStream.rangeClosed(0, 30);
        nums.filter(FilterStream2::isEven).forEach(System.out::println);
    }
    
    private static boolean isEven(int e) {
        
        return e % 2 == 0;
    }
}

To get even numbers from a stream, we pass an isEven() method reference to the filter() method.

nums.filter(FilterStream2::isEven).forEach(System.out::println);

The double colon (::) operator is used to pass a method reference. The forEach() method is a terminal operation that iterates over the elements of the stream. It takes a method reference to the System.out.println() method.

Skipping and limiting elements

The skip(n) method skip the first n elements of the stream and the limit(m) method limits the number of elements in the stream to m.

SkipLimit.java
package com.zetcode;

import java.util.stream.IntStream;

public class SkipLimit {

    public static void main(String[] args) {
        
        IntStream s = IntStream.range(0, 15);
        s.skip(3).limit(5).forEach(System.out::println);
    }
}

The example creates a stream of fifteen integers. We skip the first three elements with the skip() method and limit the number of elements to ten values.

$ java com.zetcode.SkipLimit
3
4
5
6
7

This is the output of the example.

Sorting elements

The sorted() method sorts the elements of this stream, according to the provided Comparator.

Sorting.java
package com.zetcode;

import java.util.Comparator;
import java.util.stream.IntStream;

public class Sorting {

    public static void main(String[] args) {

        IntStream nums = IntStream.of(4, 3, 2, 1, 8, 6, 7, 5);

        nums.boxed().sorted(Comparator.reverseOrder())
                .forEach(System.out::println);
    }
}

The example sorts integer elements in a descending order. The boxed() method converts IntStream to Stream<Integer>.

$ java com.zetcode.Sorting
8
7
6
5
4
3
2
1

This is the output of the example.

The next example shows how to compare a stream of objects.

Sorting2.java
package com.zetcode;

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

class Car {
    
    private String name;
    private int price;
    
    public Car(String name, int price ) {
    
        this.name = name;
        this.price = price;
    }    

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" + "name=" + name + ", price=" + price + '}';
    }
}

public class Sorting2 {

    public static void main(String[] args) {
        
        List<Car> cars = Arrays.asList(new Car("Citroen", 23000), 
                new Car("Porsche", 65000), new Car("Skoda", 18000), 
                new Car("Volkswagen", 33000), new Car("Volvo", 47000));
        
        cars.stream().sorted(Comparator.comparing(Car::getPrice))
                .forEach(System.out::println);
    }
}

The example sorts cars by their price.

List<Car> cars = Arrays.asList(new Car("Citroen", 23000), 
        new Car("Porsche", 65000), new Car("Skoda", 18000), 
        new Car("Volkswagen", 33000), new Car("Volvo", 47000));

A list of cars is created.

cars.stream().sorted(Comparator.comparing(Car::getPrice))
        .forEach(System.out::println);

A stream is generated from a list using the stream() method. We pass a reference to the Car's getPrice() method, which is used when comparing cars by their price.

$ java com.zetcode.Sorting2
Car{name=Skoda, price=18000}
Car{name=Citroen, price=23000}
Car{name=Volkswagen, price=33000}
Car{name=Volvo, price=47000}
Car{name=Porsche, price=65000}

This is the output of the example.

Unique values

The distinct() method returns a stream consisting of unique elements.

UniqueElements.java
package com.zetcode;

import java.util.Arrays;
import java.util.stream.IntStream;

public class UniqueElements {

    public static void main(String[] args) {
        
        IntStream nums = IntStream.of(1, 1, 3, 4, 4, 6, 7, 7);
        int a[] = nums.distinct().toArray();
        
        System.out.println(Arrays.toString(a));
    }
}

The example removes duplicate values from a stream of integers.

IntStream nums = IntStream.of(1, 1, 3, 4, 4, 6, 7, 7);

There are three duplicate values in the stream.

int a[] = nums.distinct().toArray();

We remove the duplicates with the distinct() method.

$ java com.zetcode.UniqueElements
[1, 3, 4, 6, 7]

This is the output of the example.

Mapping

It is possible to change elements into a new stream; the original source is not modified. The map() method returns a stream consisting of the results of applying the given function to the elements of a stream. The map() is an itermediate operation.

Mapping.java
package com.zetcode;

import java.util.Arrays;
import java.util.stream.IntStream;

public class Mapping {

    public static void main(String[] args) {
        
        IntStream nums = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8);

        int[] squares = nums.map(e -> e * e).toArray();
        
        System.out.println(Arrays.toString(squares));
    }
}

We map a transformation function on each element of the stream.

int[] squares = nums.map(e -> e * e).toArray();

We apply a lamda expression (e -> e * e) on the stream: each of the elements is squared. A new stream is created which is transformed into an array with the toArray() method.

$ java com.zetcode.Mapping
[1, 4, 9, 16, 25, 36, 49, 64]

This is the output of the example.

In the next example, we transform a stream of strings.

Mapping2.java
package com.zetcode;

import java.util.stream.Stream;

public class Mapping2 {

    public static void main(String[] args) {
        
        Stream<String> words = Stream.of("cardinal", "pen", "coin", "globe");
        words.map(Mapping2::capitalize).forEach(System.out::println);
    }
    
    private static String capitalize(String word) {
        
        word = word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase();
        return word;
    }
}

We have a stream of strings. We capitalize each of the strings of the stream.

words.map(Mapping2::capitalize).forEach(System.out::println);

We pass a reference to the capitalize() method to the map() method.

$ java com.zetcode.Mapping2
Cardinal
Pen
Coin
Globe

This is the output of the example.

Reductions

A reduction is a terminal operation that aggregates a stream into a type or a primitive.

Reduction.java
package com.zetcode;

import java.util.stream.IntStream;

public class Reduction {

    public static void main(String[] args) {

        IntStream nums = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8);
        
        int maxValue = nums.max().getAsInt();
        
        System.out.printf("The maximum value is: %d%n", maxValue);
    }
}

Getting a maximum value from a stream of integers is a reduction operations.

int maxValue = nums.max().getAsInt();

With the max() method, we get the maximum element of the stream. The method returns an Optional from which we get the integer using the getAsInt() method.

$ java com.zetcode.Reduction
The maximum value is: 8

This is the output of the example.

A custom reduction can be created with the reduce() method.

Reduction2.java
package com.zetcode;

import java.util.stream.IntStream;

public class Reduction2 {

    public static void main(String[] args) {
        
        IntStream nums = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8);
        
        int product = nums.reduce((a, b) -> a * b).getAsInt();
        
        System.out.printf("The product is: %d%n", product);
    }
}

The example returns a product of the integer elements in the stream.

$ java com.zetcode.Reduction
The product is: 40320

This is the output of the example.

Collection operations

A collection is a terminal reduction operation which reduces elements of a stream into a Java collection, string, value, or specific grouping.

Collecting.java
package com.zetcode;

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

class Car {

    private String name;
    private int price;

    public Car(String name, int price) {

        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" + "name=" + name + ", price=" + price + '}';
    }
}

public class Collecting {

    public static void main(String[] args) {

        List<Car> cars = Arrays.asList(new Car("Citroen", 23000),
                new Car("Porsche", 65000), new Car("Skoda", 18000),
                new Car("Volkswagen", 33000), new Car("Volvo", 47000));
                
        List<String> names = cars.stream().map(Car::getName)
                .filter(name -> name.startsWith("Vo"))
                .collect(Collectors.toList());
        
        for (String name: names) {
            System.out.println(name);
        }
    }
}

The example creates a stream from a list of car object, filters the cars by the their name, and returns a list of matching car names.

List<String> names = cars.stream().map(Car::getName)
        .filter(name -> name.startsWith("Vo"))
        .collect(Collectors.toList());

At the end of the pipeline, we use the collect() method to transform

$ java com.zetcode.Collecting
Volkswagen
Volvo

This is the output of the example.

In the next example, we use the collect() method to group data.

Collecting2.java
package com.zetcode;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Collecting2 {

    public static void main(String[] args) {

        List<String> items = Arrays.asList("pen", "book", "pen", "coin",
                "book", "desk", "book", "pen", "book", "coin");

        Map<String, Long> result = items.stream().collect(
                Collectors.groupingBy(
                        Function.identity(), Collectors.counting()
                ));

        for (Map.Entry<String, Long> entry : result.entrySet()) {

            String key = entry.getKey();
            Long value = entry.getValue();

            System.out.format("%s: %d%n", key, value);
        }
    }
}

The code example groups elements by their occurrence in a stream.

Map<String, Long> result = items.stream().collect(
        Collectors.groupingBy(
                Function.identity(), Collectors.counting()
        ));

With the Collectors.groupingBy() method, we count the occurrences of the elements in the stream. The operation returns a map.

for (Map.Entry<String, Long> entry : result.entrySet()) {

    String key = entry.getKey();
    Long value = entry.getValue();

    System.out.format("%s: %d%n", key, value);
}

We go through the map and print its key/value pairs.

$ java com.zetcode.Collecting2
desk: 1
book: 4
pen: 3
coin: 2

This is the output of the example.

This part of the Java tutorial covered streams.