Exploring Numeric Streams In Java

Introduction

Java Numeric Streams are designed for primitive values in a Stream. The Stream class has three different variations

  1. IntStream.
  2. LongStream.
  3. DoubleStream.

These variations are created specifically for the primitives in stream operations, otherwise we must use the Boxed version of Stream in the form of Stream<Integer>, Stream<Long>, and Stream<Double>.

For example:

Stream

Stream.of(1,2).forEach(x -> {
      System.out.println(x.getClass());
});

class java.lang.Integer
class java.lang.Integer

IntStream elements are primitives.

IntStream.of(1,2).forEach(x -> {
    System.out.println(x.getClass()); //compilation error.
});

Cannot invoke getClass() on the primitive type int

Other differences than Stream

  1. The usual Stream<Integer> must work with Boxed values which takes more memory, an ‘int’ is 4 bytes whereas ‘Integer’ is an object, and it takes 16 bytes to store int value. For example,
  2. To perform specific operations like sum or count, internally Unboxing happens in Streams, meaning Integer is converted to int. This is simplified if we use the specific Numeric Stream.
    1. Boxing: converting primitive type to Wrapper class Type: int to Integer
    2. Unboxing: converting Wrapper class Type to primitive: Integer to int
  3. The specific streams (IntStream, LongStream, DoubleStream) have a collection of utility methods, like ‘summaryStatistics()’, ‘range’, ‘rangeClosed’, ‘mapToObj’ etc., which will explore in the article.
  4. For efficiently working with primitive specialized Streams (IntStream, LongStream, DoubleStream), specialized primitive interfaces like (IntFunction, IntUnaryOperator, IntConsumer, IntSupplier, etc) are provided. The function in these interfaces takes specialized interfaces as parameters, for example: ‘mapToObj’ function takes ‘IntFunction’ as the parameter.
  5. The specialized interfaces work with specific primitive implicitly, for example, ‘IntFunction’ has one abstract ‘apply’ method that takes ‘int’ value as the parameter for ‘LongFunction’, ‘long’ value is accepted.

Understanding the above differences with examples

Stream example: Adding the elements in the Stream

Stream.of(1,2,3,4,5)
    .reduce(0, (x, y) -> x + y);

above code can easily be transformed using IntStream.

IntStream example:

IntStream.of(1,2,3,4,5)
    .sum();
  1. The Stream example is taking 5 times more memory than the IntStream example.
  2. In the Stream example Boxed (Integer) value is returned rather than primitive, in the second example, ‘int’ value is returned.  
  3. In the IntStream example, we have created the primitive stream using the range() method. The range method returns a sequential ordered IntStream where 1 is Inclusive and 6 is exclusive and at every step, each element of Stream is incremented by 1.
  • boxing and unboxing:  The primitive to Wrapper transformation is performed using ‘boxed’ function.
public static List<Integer> boxing(int[] arr){
    return Arrays.stream(arr)
     .boxed()
     .collect(Collectors.toList());
}

The unboxing operation can be performed using the ‘mapToInt’ function, i.e., conversion of Integer to ‘int’  

public static int[] unboxing(List<Integer> list){
    return list.stream()
     .mapToInt(Integer :: intValue)
      .toArray();
}

In the example we have converted Stream to IntStream using mapToInt, the method returns IntStream consisting of the results of applying the given mapper function to the elements of this stream. Similarly, for long we must use mapToLong and for double, mapToDouble should be used.

  • range and rangeClosed:  Both these methods are used to create IntStream, the only difference is the rangeClosed method includes ending it, whereas the range method excludes it
  • mapToObj:  The ‘mapToObj’ function converts each element of the numeric stream to some object, for example: Converting IntStream of elements to String
IntStream.rangeClosed(1, 5)
    .mapToObj(x -> String.valueOf(x))
    .forEach(x -> {
        System.out.println(x);
    });

The ‘mapToObj’ functions behavior looks like the ‘map’ function, the difference is, in IntStream the map function takes ‘IntUnaryOperator’ as the parameter, the IntUnaryOperator represents a function that takes one argument as a parameter and operates on it, but the argument and return type are of ‘int’ type.

IntStream.rangeClosed(1, 5)
    .map(x -> String.valueOf(x)) //compilation error
    .forEach(x -> {
        System.out.println(x);
    });

Type mismatch: cannot convert from String to int

IntStream.rangeClosed(1, 5)
    .map(x -> x + 1)
    .forEach(x -> {
        System.out.println(x);
    });

Note: LongStream has LongUnaryOperator interface and in DoubleStream  has DoubleUnaryOperator which has the same behavior as IntUnaryOperator

  • summaryStatistics:  The ‘summaryStatistics’ function helps in getting various summary data about the elements of the stream, like average, count, sum, min, max. The summaryStatistics function upon operating on IntStream for example returns IntSummaryStatistics object which basically collects count, sum, min, max results.
IntSummaryStatistics statistics = IntStream.rangeClosed(1, 5)
    .summaryStatistics();
System.out.println(statistics);

returns,                                                                                 

   


Similar Articles