4

Why does PrimitiveIterator.OfInt extend Iterator<Integer> but IntStream doesn't extend Stream<Integer>?

I'm trying to design a primitive collection type (similar to the interfaces from the Apache Commons Primitives library (http://commons.apache.org/dormant/commons-primitives/) and trying to be as consistent and compatible with the collections library, but I can't decide whether I should make my ByteList extend List<Byte> or not.

My guess is that this is because Iterator has direct syntax support in the language (i.e. for loops using iterators) so it's worth making the iterator compatible with that syntax even though it forces boxing, but I'm curious if anyone knows if there's a deeper reason. Thanks!

rsalkeld
  • 69
  • 2
  • My guess would be that they wanted to avoid cluttering the interface - in order to extend `Stream`, they would have had to keep all methods, duplicating them with a primitive version. Perhaps the fact that the interface for `Iterater` is a lot smaller (just two methods duplicated per primitive type) made it feel acceptable to the designers in this case. – Hulk Oct 23 '16 at 22:33
  • 3
    `PrimitiveIterator.OfInt` doesn't have outright naming conflicts with `Iterator`; it only adds one method, `nextInt`, whereas `IntStream` has all sorts of different methods -- especially `filter`, which can't really be overloaded usefully with `IntPredicate` as well as `Predicate`. – Louis Wasserman Oct 23 '16 at 22:52
  • The primitive versions of `Spliterator` corresponding to the primitive iterators, e.g. [Spliterator.OfInt](https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.OfInt.html), implement their generic superinterface `Spliterator` an provide both wrapper- and primitive versions for their methods. But they only have two methods anyway, and overloading worked fine there. – Hulk Oct 23 '16 at 22:52
  • In this [other answer](http://stackoverflow.com/a/22919112/697630) I had addressed similar concerns. You may want to take a look. With a bit of luck you may find some additional material for your investigation there. – Edwin Dalorzo May 03 '17 at 19:23

1 Answers1

2

It is seldom possible to tell why the JDK APIs were designed the way they did it, but in this case we can easily see that trying to make the APIs of Stream<Integer> and IntStream work together would be hard to do because there are a number of ambiguities in the method definitions of both interfaces.

Consider the following:

interface Stream<T> {
    Stream<T> distinct();
    Optional<T> findFirst();
}

interface IntStream extends Stream<Integer> {
    IntStream distinct();
    OptionalInt findFirst();
}

The second interface would not event compile since the signature of the methods is the same, but the return type is different in the second interface.

Even compatible methods may become difficult to use when we provide multiple implementations of the same method that accept lambdas. Lambdas and overloading of methods typically do not play well together because a given lambda may implement multiple functional interfaces. For example:

interface Stream<T> {
    Stream<T> filter(Predicate<T> p);
    <S> Stream<S> map(Function<T,S> mapper);
}

interface IntStream extends Stream<Integer> {
    IntStream filter(IntPredicate p);
    IntStream map(IntUnaryOperator mapper);
}

Now, if you have a invocation like stream.filter(n -> n > 10) this lambda can actually implement both Predicate<Integer> or IntPredicate and now the API user is forced to do some sort of disambiguation, e.g. (int n) -> n > 10, since the compiler cannot tell the difference.

I suppose, in the long term, this may hinder the evolution of the Stream and IntStream APIs.

Edwin Dalorzo
  • 76,803
  • 25
  • 144
  • 205