Introduction

A DoubleStream is a sequence of double-valued elements that supports sequential and parallel aggregate operations. In this post, we will learn about the Java DoubleStream.

Java DoubleStream - Creation

There are various static utility methods to create a DoubleStream in Java which result in either a finite or infinite DoubleStream.

Finite DoubleStream creation

DoubleStream#empty method returns an empty DoubleStream.

DoubleStream#of

There are two overloaded of methods - one takes a single double value and one takes a var-args of double values. It returns a sequential ordered DoubleStream with the passed elements.

DoubleStream.of(1.4d, 2.2d, 3.1d)
        .forEach(System.out::println);

Prints,

1.4
2.2
3.1

Note: Unlike an IntStream or a LongStream, a DoubleStream does not have a range or a rangeClosed method as there are an infinite values (mathematically) between a specified range.

Infinite DoubleStream creation

Let us look at ways to generate an infinite DoubleStream.

DoubleStream#generate

The generate method accepts a DoubleSupplier (a Supplier that returns double values) and returns an infinite sequential unordered stream where each element in the stream is what the supplier returns.

Example: To generate a stream of constants (same value here), we can do like,

DoubleStream.generate(() -> 1.2)
       .limit(3)
        .forEach(System.out::println);
System.out.println();

Prints,

1.2
1.2
1.2

Note that I added an intermediate operation (limit) to keep the resultant stream to a finite length.

DoubleStream#iterate

Method signature:

DoubleStream iterate(final double seed, final DoubleUnaryOperator f)

It takes a seed value and a DoubleUnaryOperator. A DoubleUnaryOperator takes a double argument and returns a double.

The iterate method returns a sequential ordered infinite stream generated by iterative application of the passed function. It starts with the initial seed element, which is the first element of the DoubleStream. The second element of the stream is determined by applying the passed function to the seed value. The third element is the result of applying the function to the second value, and so on.

Example: The below code starts with a seed value of 1.2 and generates an infinite DoubleStream by adding 0.5 to the previous value.

DoubleStream.iterate(1.2, d -> d + 1)
        .limit(5)
        .forEach(System.out::println);

The above code outputs,

1.2
1.7
2.2
2.7
3.2

DoubleStream#iterate to generate a finite stream (Java 9)

In Java 9, they added an overloaded generate method which will generate a finite DoubleStream. In addition to the set of parameters seen above (in the generate method), it takes a DoublePredicate. A DoublePredicate is a Predicate operating solely on double value (i.e., it takes a double value and returns a boolean result).

The method signature of this overloaded generate method is as follows:

DoubleStream iterate(double seed, DoublePredicate hasNext, DoubleUnaryOperator next)

It returns a sequential ordered stream produced by iterative application of the passed function. As seen before, it starts with the seed value as the first element of the stream. But before it generates an element, it calls the passed predicate. The element is included in the stream only if the predicate returns true for that element.

Note: If the predicate returns false for the seed element, it will result in an empty stream.

DoubleStream.iterate(1.2, d -> d < 3, d -> d + 0.5)
        .forEach(System.out::println);

In the above code, we started with a seed value of 1.2 and the function generates the next element by adding 0.5 to the previous element. The DoublePredicate checks if the element is less than 3. Only if that condition is satisfied, the element will be added to the stream. Once it encounters a value for which the predicate returns false, no more elements will be added to the stream.

The code prints,

1.2
1.7
2.2
2.7

This method is similar to a traditional for loop:

for (double d = 1.2; d < 3; d += 0.5) {
    System.out.println(d);
}

Java DoubleStream - Intermediate Operations

Let us look at the intermediate operations in a Java DoubleStream.

Filter

The filter operation on a DoubleStream accepts a DoublePredicate and filters the elements that match the predicate.

The below code generates an infinite DoubleStream and filters the even values alone.

DoubleStream.iterate(1, d -> d + 1)
        .filter(d -> d %2 == 0)
        .limit(3)
        .forEach(System.out::println);

Prints,

2.0
4.0
6.0

Map

The map operation takes in a DoubleUnaryOperator. We can use it to transform/map each of the stream elements to a new value. In the below example, we double each of the elements in the DoubleStream.

DoubleStream.of(1.0, 2.0, 3.0)
        .map(d -> d * 2)
        .forEach(System.out::println);

Outputs,

2.0
4.0
6.0

FlatMap

We use the flatMap when a mapping operation returns a DoubleStream - thus it flattens the elements in the stream (result of the mapping operation).

As an example, let us start with a finite DoubleStream and map each element to a DoubleStream.

DoubleStream.of(1.0, 5.0, 10.0)
        .flatMap(d -> DoubleStream.of(d + 1, d + 2))
        .forEach(System.out::println);

For each element, we return a DoubleStream of two elements - the stream_element + 1 and the stream_element+2. The result is,

2.0
3.0
6.0
7.0
11.0
12.0

MapToObj

The mapToObj operation takes a DoubleFunction which maps the double value to a value of any type. In the below example, we map each double value to a string.

DoubleStream.of(1.0, 5.0, 10.0)
        .mapToObj(d -> d + "-value")
        .forEach(System.out::println);

Outputs,

1.0-value
5.0-value
10.0-value

MapToInt and MapToLong

The mapToInt and mapToLong operations take a DoubleToIntFunction and DoubleToLongFunction, respectively. We use them to map a double to an int and a long, respectively.

DoubleStream.of(1.0, 5.0, 10.0)
        .mapToInt(d -> (int) d)
        .mapToObj(d -> d + "-value")
        .forEach(System.out::println);

Prints,

1-value
5-value
10-value

Limit and Skip

We have already seen the application of the limit operation. It limits the DoubleStream to the specified number of elements. Similarly, the skip method skips the specified number of elements in the initial stream.

DoubleStream.of(1.0, 5.0, 10.0, 20.0, 30.0)
        .skip(2)
        .forEach(System.out::println);

In the DoubleStream, we skip the first 2 elements and print the rest. The above code prints,

10.0
20.0
30.0

Boxed

The boxed operation converts a DoubleStream to a Stream<Double>. In other words, it boxes the primitive double value to a Double object.

DoubleStream.of(1.0, 5.0, 10.0)
        .boxed()
        .map(Double::intValue)
        .forEach(System.out::println);

Prints,

1
5
10

After boxing to a Double, we can access the methods in the Double class.

TakeWhile and DropWhile

The takeWhile and dropWhile operations were added in Java 9. Refer to the post on Stream takeWhile and DropWhile to learn more.

Java DoubleStream - Terminal Operations

Let us look at the terminal operations on a Java DoubleStream.

Min, Max, Sum and Average

These are simple terminal operations.

System.out.println(DoubleStream.of(5.1, 4.3, 2.2).min()); //OptionalDouble[2.2]
System.out.println(DoubleStream.of(5.1, 4.3, 2.2).max()); //OptionalDouble[5.1]
System.out.println(DoubleStream.of(5.1, 4.3, 2.2).sum()); //11.6
System.out.println(DoubleStream.of(5.1, 4.3, 2.2).average()); //OptionalDouble[3.8666666666666667]

AllMatch, AnyMatch and NoneMatch

Refer to the post on Java Streams allMatch, anyMatch and noneMatch for a detailed explanation of these methods. They work the same way for a double stream as well.

FindFirst and FindAny

You can read the post on Java streams FindFirst and FindAny where I have explained in detail about these methods.

DoubleStream Reduce

There are two overloaded reduce methods. The first one takes a DoubleBinaryOperator, and the second takes a DoubleBinaryOperator and an identity value. A DoubleBinaryOperator takes two double values and returns a new double value (result of applying the function).

Let us reduce a double stream by adding the elements together.

OptionalDouble sum = DoubleStream.of(5.0, 4.3, 2.0)
            .reduce(Double::sum);
System.out.println(sum); //OptionalDouble[11.3]

Since we used the reduce method without an identity value, it returns an OptionalDouble.

When using an identity value, it returns a primitive double.

double s = DoubleStream.of(5.0, 4.3, 2.0)
        .reduce(0, Double::sum);
System.out.println(s); //11.3

DoubleStream Collect

The method signature of the collect operation is:

<R> R collect(Supplier<R> supplier,
              ObjDoubleConsumer<R> accumulator,
              BiConsumer<R, R> combiner);

It performs a mutable reduction on the elements of the DoubleStream. As an example, let us use the collect operation to add the stream elements into an ArrayList. (This is a contrived example because Collectors.asList() would be simpler here).

List<Double> result = DoubleStream.of(5.1, 4.3, 2.2)
        .collect(() -> new ArrayList<>(),
                (list, doubleVal) -> list.add(doubleVal),
                (list1, list2) -> list1.addAll(list2));
  • Supplier: We passed a lambda expression to create a new ArrayList. This creates a new mutable result container.
  • Accumulator: The passed function adds the double element from the stream to the list.
  • Combiner: This takes in two partial result containers and merges them (folds the second into the first). Note that a combiner will only be called when using a parallel stream (to merge the partial result from multiple threads).

Using method references, we can write as:

List<Double> result = DoubleStream.of(5.1, 4.3, 2.2)
        .collect(ArrayList::new,
                ArrayList::add,
                ArrayList::addAll);
System.out.println(result); //[5.1, 4.3, 2.2]

Conclusion

This concludes the post on the Java DoubleStream. I recommend referring to the post on IntStream and LongStream as well.