8

(This is probably related to https://stackoverflow.com/a/30312177/160137, but I'm afraid I still don't get it. So I'm asking my question this way, in the hope that it'll lead to an answer I can more easily understand.)

Normally when I have a Stream I can convert it to a collection using one of the static methods in the Collectors class:

List<String> strings = Stream.of("this", "is", "a", "list", "of", "strings")
    .collect(Collectors.toList());

The similar process doesn't work on primitive streams, however, as others have noticed:

IntStream.of(3, 1, 4, 1, 5, 9)
    .collect(Collectors.toList());  // doesn't compile

I can do this instead:

IntStream.of(3, 1, 4, 1, 5, 9)
    .boxed()
    .collect(Collectors.toList());

or I can do this:

IntStream.of(3, 1, 4, 1, 5, 9)
    .collect(ArrayList<Integer>::new, ArrayList::add, ArrayList::addAll);

The question is, why doesn't Collectors.toList() just do that for primitive streams? Is it that there's no way to specify the wrapped type? If so, why doesn't this work either:

IntStream.of(3, 1, 4, 1, 5, 9)
    .collect(Collectors.toCollection(ArrayList<Integer>::new)); // nope

Any insight would be appreciated.

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
kousen
  • 2,897
  • 3
  • 28
  • 26
  • 10
    Because (even in Java-8), there is no `List`. Primitive types **are not** `Object`(s). – Elliott Frisch Aug 15 '16 at 23:09
  • 3
    The generics in the signatures simply won't work with primitive types. Simple as this. – Dici Aug 15 '16 at 23:09
  • 4
    There's no functional difference between explicitly calling `boxed()` and allowing boxed collectors on primitive streams. If you're asking why it wasn't allowed for convenience, maybe the designers wanted to make the boxing readily apparent. – shmosel Aug 15 '16 at 23:10
  • 4
    @ElliottFrisch, Dici I think OP understands that. The question is why couldn't boxed collectors be used on primitive streams? For example, an `IntStream` might accept a `Collector super Integer, A, R>`. – shmosel Aug 15 '16 at 23:11
  • 1
    @shmosel I wonder if the compiler would even allow what you call "boxed collectors" (although I'm not sure what you mean by that). They could have duplicated all collectors to make them primitive collectors, but that's not so great – Dici Aug 15 '16 at 23:14
  • 1
    @Dici Of course it would. In fact any of the custom methods that accept `IntConsumer`, `IntPredicate`, `IntFunction` etc. could just as easily have used their boxed equivalents (`Consumer` etc.) They just use specialized primitive interfaces for performance reasons. This supports my theory that it was not implemented because `IntStream` was specifically designed to support primitive-optimized operations, as opposed to general-purpose integer operations. – shmosel Aug 15 '16 at 23:24
  • @shmosel again, that depends what you meant by "boxed collectors". But overall I agree, I think making these streams more general would have brought so much duplication that it was not worth it. – Dici Aug 15 '16 at 23:28
  • @Dici I included an example of what I meant in my comment. And the answer linked to at the top of the post also mentions that adding general methods to primitive streams would result in "lots more API surface". – shmosel Aug 15 '16 at 23:33

3 Answers3

3

Well, it would be no problem to let IntStream provide a method with the signature
<R,A> R collect(Collector<? super Integer,A,R> collector) performing an implicit boxing. It could be implemented as simple as return boxed().collect(collector);.

The question is “why should it” or the other way round: why do the primitive stream specializations exist at all?

They merely exist for performance reasons, to offer stream operations without boxing overhead. So it’s an obvious design decision not to include any operation which already exists in the generic Stream interface as for all these you can simply invoke boxed().genericOperation(…).

The answer, you have linked addresses a related, but different idea. It’s about providing a collect method not accepting the generic Collector, but a primitive specialization like Collector.ofInt which could collect int values without boxing, but for a collector which produces a List<Integer>, unavoidably containing boxed values, it wouldn’t have any benefit. Amongst the prebuilt collectors, only a few really could avoid boxing. All of them are provided as explicit terminal operations on the primitive streams (count(), sum(), min(), max(), summaryStatistics(), …)

It’s a trade-off between number of classes/interfaces and potential performance gain. In the case of streams in general, the decision was to create IntStream, LongStream and DoubleStream, but in case of the collectors, the decision was not to add such specializations.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
2
IntStream.of(3, 1, 4, 1, 5, 9)
    .collect(Collectors.toList());  // doesn't compile

The stream is int, the generic method toList() assumes an object. No match.

IntStream.of(3, 1, 4, 1, 5, 9)
    .boxed()
    .collect(Collectors.toList());

The stream is Integer, the generic method toList() assumes an object. Match!

IntStream.of(3, 1, 4, 1, 5, 9)
    .collect(ArrayList<Integer>::new, ArrayList::add, ArrayList::addAll);

The stream is int, and it's like fixing the generic method toList() to Integer. Except that IntStream doesn't have a collect() method that accepts a collector, only a 3-parameter method.

Mark Jeronimus
  • 9,278
  • 3
  • 37
  • 50
1

For primitive types List<> is an added suboptimal usage. Hence toArray was deemed sufficient and adequate (=optimal usage).

int[] array = IntStream.of(3, 1, 4, 1, 5, 9).toArray();
Joop Eggen
  • 107,315
  • 7
  • 83
  • 138