Primitive stream vs object stream and actual boxing that occurs
Asked Answered
I

2

6

So I understand you can have object streams, i.e. Stream<T> and specialist primitive streams, e.g. IntStream, DoubleStream, etc. One of the benefits of the latter is to avoid autoboxing.

Also if we take IntStream as an example, it has specialised operations such as filter that accepts an IntPredicate.

I wondered if I had an IntStream vs a Stream<Integer> at which point you're saving on boxing, e.g.

intstream.filter(x -> x >= 50).forEach(x -> System.out.println(x));

vs

stream.filter(x -> x >= 50).forEach(x -> System.out.println(x));

In the first example, there is no boxing or unboxing that I can see. In the second example where is the boxing / unboxing occurring? Because if stream is a Stream<Integer> and the filter accepts a Predicate<Integer> then surely there's no need to box/unbox, and the same for IntConsumer vs Consumer<T>?

Item answered 4/2, 2016 at 18:49 Comment(0)
F
5

With Stream<Integer> your stream is already boxed. So depending on how you created it there are several possibilities:

  • You have initially boxed values. For example, you had List<Integer> list as the input and wrote:

    stream = list.stream();
    
  • You boxed your values when creating the stream. For example, you created it like this:

    stream = Stream.iterate(1, x -> x+1);
    

    In this case the first argument is boxed to Integer.valueOf(1) and unboxing/boxing also occurs on every lambda (which is UnaryOperator<Integer>) invocation. So effectively you have:

    stream = Stream.iterate(Integer.valueOf(1), x -> Integer.valueOf(x.intValue()+1));
    
  • You boxed your source explicitly via upstream intermediate operation. For example:

    stream = IntStream.range(0, 1000).boxed();
    

    In this case boxing is performed inside the .boxed() operation (which is a shortcut for .mapToObj(Integer::valueOf)).

Faculty answered 5/2, 2016 at 3:40 Comment(2)
what I'm trying to get at here is if I had a source of say a List<Integer> then I've already had to box all its elements. Considering this, I'm wondering whether it would be best to use a Stream<Integer> or IntStream to process it - so I'm looking at the boxing/unboxing that could occur during a the pipeline. For a start getting an IntStream would require unboxing all Integer objects from the list, but then you would possibly have no more boxing/unboxing. But with a Stream<Integer> I cannot see any further boxing only unboxing. Literature commonly refers to frequent boxing thoughItem
@Tranquility, you should not care about unboxing: it's absolutely cheap (just single dereference). What's expensive is the boxing (an actual object allocation in worst case). Also note that every single numerical operation with Integer (addition, comparison, etc.) requires unboxing.Faculty
M
6

The unboxing happens inside the predicate: in stream.filter(x -> x >= 50), the Predicate essentially becomes (Integer x) -> x.intValue() >= 50, and intValue() is the unboxing step. Additionally, System.out.println does some unboxing itself, in the implementation of Integer.toString that ends up being called.

Masqat answered 4/2, 2016 at 18:54 Comment(1)
yes this is what I thought so forgetting about the boxing that may have occured in populating the source of the Stream<Integer> there is not any further boxing but usually unboxing to evaluate the integer elements during lamba expressions? I wondered if when the stream pipeline passes an element to say filter whether it goes in as an int and therefore gets boxed into an Integer - but I doubt this would happen. The point being the benefit of having an IntPredicate argument is not to stop boxing, but to avoid unboxing during the lambda?Item
F
5

With Stream<Integer> your stream is already boxed. So depending on how you created it there are several possibilities:

  • You have initially boxed values. For example, you had List<Integer> list as the input and wrote:

    stream = list.stream();
    
  • You boxed your values when creating the stream. For example, you created it like this:

    stream = Stream.iterate(1, x -> x+1);
    

    In this case the first argument is boxed to Integer.valueOf(1) and unboxing/boxing also occurs on every lambda (which is UnaryOperator<Integer>) invocation. So effectively you have:

    stream = Stream.iterate(Integer.valueOf(1), x -> Integer.valueOf(x.intValue()+1));
    
  • You boxed your source explicitly via upstream intermediate operation. For example:

    stream = IntStream.range(0, 1000).boxed();
    

    In this case boxing is performed inside the .boxed() operation (which is a shortcut for .mapToObj(Integer::valueOf)).

Faculty answered 5/2, 2016 at 3:40 Comment(2)
what I'm trying to get at here is if I had a source of say a List<Integer> then I've already had to box all its elements. Considering this, I'm wondering whether it would be best to use a Stream<Integer> or IntStream to process it - so I'm looking at the boxing/unboxing that could occur during a the pipeline. For a start getting an IntStream would require unboxing all Integer objects from the list, but then you would possibly have no more boxing/unboxing. But with a Stream<Integer> I cannot see any further boxing only unboxing. Literature commonly refers to frequent boxing thoughItem
@Tranquility, you should not care about unboxing: it's absolutely cheap (just single dereference). What's expensive is the boxing (an actual object allocation in worst case). Also note that every single numerical operation with Integer (addition, comparison, etc.) requires unboxing.Faculty

© 2022 - 2024 — McMap. All rights reserved.