12

I was wondering about the Java 8 streams (Stream<E>), they have the following methods:

  • forEach(Consumer<? super E> action)
  • forEachOrdered(Consumer<? super E> action)

What were the arguments against not supplying the following signature?

  • forEachOrdered(BiConsumer<Integer, ? super E> action)
    • Which would then return the index of the item in the stream and the item itself.

With this overload it would be possible to actually use the index in case the stream was ordered.

I am really curious to see what the arguments are against it.

Edit, the same actually holds for Iterator<E> with forEachRemaining, and possibly more classes.
If none of the classes provide such option, then I suspect it has been considered for Java 8 and denied.

skiwi
  • 66,971
  • 31
  • 131
  • 216

4 Answers4

9

indexing every element requires a sequential assignment of the indexes. this would defeat the point of parallel operations, since each operation would have to synchronize to get its index.

aepurniet
  • 1,719
  • 16
  • 24
  • 1
    I am talking specifically about ordered streams, if you decide to stand by your answer, can you explain why it is not possible to index in a parallel ordered stream? – skiwi Apr 01 '14 at 15:52
  • If you wanted to, you could keep a counter in the consumer itself for operations known to be executed in order, so why would you need the extra signature? – Louis Wasserman Apr 01 '14 at 16:17
  • 1
    an Ordered stream is not the same thing as a Indexed stream. in fact I dont think java has an Indexed Characteristic on the Spliterator Interface. If i really needed an Index along with a datum, and i wanted to use the stream interface, I would make the index part of the data structure. But i would investigate avoiding indexes altogether. – aepurniet Apr 01 '14 at 16:53
  • 1
    @LouisWasserman I wouldn't go quite so far as to say nonsensical, but parallelizing indexed operations can become quite difficult in the general case. Also, as aepurniet pointed out, make sure to distinguish between processing order and encounter order. – Stuart Marks Apr 01 '14 at 16:59
6

Streams and Iterators do not have to be finite. Both Stream::generate and Stream::iterate return infinite Streams. How would you handle indexing with an infinite stream? Let the index overflow to negative numbers? Use a BigInteger (and potentially run out of memory)?

There isn't a good solution to handling indexing for infinite streams, so the designers (correctly, in my opinion) left it out of the API.

Jeffrey
  • 44,417
  • 8
  • 90
  • 141
  • 3
    The same issue occurs with `Collectors.counting()`, there a `long` is used, but it may overflow aswell. So on that point either your argument is not good enough, or it is a good argument against `Collectors.counting()` aswell. I do not see any other issues though with infinite streams and iterators (apart from that they never stop) – skiwi Apr 01 '14 at 15:10
  • 1
    @skiwi Well, if you tried to use half of the methods in `Collectors` on an infinite `Stream` you would either run out of memory or introduce an infinite loop. `Collectors` provides utility methods to handle a majority of use cases. Most of the time, you won't actually be dealing with an infinite `Stream`, so `Collectors` is warranted. – Jeffrey Apr 01 '14 at 15:14
2

Adding a single method providing an index would require all implementation methods to be doubled to have one maintaining an index and one without. There’s more to it than visible in the API. If you are curious you may look at the type tree of the internal interface java.util.stream.Sink<T> to get an idea. All of them would be affected. The alternative would be to always maintain an index even if it is not required.

And it adds an ambiguity. Does the index reflect the source index, i.e. does not change on filtering, or is it a position in the final stream? On the other hand you can always insert a mapping from an item type to a type holding the item and an index at any places in the chain. This would clear the ambiguity. And the limitations to that solution are the same that a JRE provided solution would have.

In case of an Iterator the answer is even simpler. Since forEachRemaining must be provided as a default interface method it cannot add the maintenance of an index. So at the time it is invoked, it doesn’t know how many items have been consumed so far. And starting the count with zero at that time, ignoring all previous items would be a feature that a lot of developers would question even more.

Holger
  • 285,553
  • 42
  • 434
  • 765
0

I have read all above answers, however, personally i disagree with them. I think some method(e.g. indexed()) should be added and it can be executed sequentially, even in parallel stream because this method will be verify fast, no need to execute in parallel. You can add 'index' by map. for example:

List<String> list = N.asList("a", "b", "c");
final AtomicLong idx = new AtomicLong(0);
list.stream().map(e -> Indexed.of(idx.getAndIncrement(), e)).forEach(N::println);

Or you can use third library: abacus-common, the code will be:

List<String> list = N.asList("a", "b", "c");
Stream.of(list).indexed().forEach(N::println);
// output:
// [0]=a
// [1]=b
// [2]=c

Disclosure: I'm the developer of abacus-common.

user_3380739
  • 1
  • 14
  • 14