10

Why didn't Java choose this signature <T> Stream<T> Stream.generate(Supplier<? extends T> supplier) over this one <T> Stream<T> Stream.generate(Supplier<T> supplier) ?

I mean the below example (doesn't compile) is correct as a supplier of Strings is also valid in a stream of CharSequences, no ?

Supplier<String> constantHello = () -> "Hello";

long count = Stream.<CharSequence>generate(constantHello).count();
Lii
  • 11,553
  • 8
  • 64
  • 88
marsouf
  • 1,107
  • 8
  • 15
  • Make an example that compiles. – Thorbjørn Ravn Andersen Aug 19 '17 at 15:34
  • 2
    if i can make an example that compiles (and i can), there will be no problem lol. – marsouf Aug 19 '17 at 15:35
  • This example doesn't really make sense. A `Supplier` generates a `Stream`. You can add a terminal operations that consumes `CharSequence` or even `Object`. PECS. – Boris the Spider Aug 19 '17 at 15:36
  • 1
    a `Supplier ` generates string objects which are valid in the context of a `Stream ` because we can reference a `String` through a `CharSequence` variable. – marsouf Aug 19 '17 at 15:37
  • 1
    No. Generics are invariant. See, for example, [this answer](https://stackoverflow.com/a/18666878/2071828). You have a `Stream`. That is what you have. – Boris the Spider Aug 19 '17 at 15:39
  • @BoristheSpider, i did understand the answer you pointed me at, but i still believe that a `Stream ` can be generated from a `Supplier ` because in the context of a `CharSequence`, a `String` is also valid. So why did java didn't reflected this on the signature of `Stream.generate` ? – marsouf Aug 19 '17 at 15:45
  • 3
    As a motivation for why `Supplier extends T>` would be desirable here, suppose I have a `Supplier` and want to return from my API a `Stream`. I doubt we can easily answer why the method is declared the way it is, though, unless somebody such as @StuartMarks can explain it. Also note that you can get around this by saying `generate(constantHello::get)` or `.map(s -> (CharSequence) s)`. – Radiodef Aug 19 '17 at 15:46
  • @Radiodef can you explain to me why the tweak `constantHello::get` did the thing here ? – marsouf Aug 19 '17 at 15:53
  • 2
    @BoristheSpider The example by Radiodef shows that this is in fact a reasonable use case, and I don't see a reason why they should not have supported `? extends T` (so +1 for the question). @marsouf : With the `::get` trick, you are basically creating a new supplier that has the type that is required there. (A very simplified explanation - there is some pretty hardcore type inference going on under the hood...) – Marco13 Aug 19 '17 at 15:56
  • It just lets type inference do its thing. `constantHello.get()` returns a `String` which is assignable to a `CharSequence`, so the method reference is valid. ([Simplified example](http://ideone.com/c1UN8G)) – Radiodef Aug 19 '17 at 15:56
  • @marsouf The current form of the question might lead to answers focussing on irrelevant points (e.g. "It does not work because you wrote ``", or workaround suggestions like the `::get`). If you made clear that you cannot create a `Stream st = Stream.generate(s);` from a `Supplier s` (although there is no reason of why this should not be possible), then this could be avoided (But of course, that's up to you). – Marco13 Aug 19 '17 at 16:01
  • @Marco13 but it's not a `Stream`, it's a `Stream`. You can of course assign to `String extends CharSequence>`. – Boris the Spider Aug 19 '17 at 16:03
  • As i have heard before, the expression `constantHello::get` is polymorphic, i mean it gets its type from the context, in this example the context was `Supplier ` so the type inference did the thing but for bare `constantHello`, this is not the case no ? – marsouf Aug 19 '17 at 16:04
  • 2
    @BoristheSpider That's the point. If they had written `? extends T`, then it **could** be a (real) `Stream`. The main point of generics being invariant is to keep type safety. But by streams being "read only", this is not applicable here. – Marco13 Aug 19 '17 at 16:04
  • @marsouf that's not what polymorphic means - your comment is rather ... confusing. – Boris the Spider Aug 19 '17 at 16:04
  • for example a bare `() -> "Hello"` has an unknown type unless we cast it or reference it from a variable with the appropriate type. – marsouf Aug 19 '17 at 16:18
  • It doesn't have an unknown type; is has a very specific and defined type. It's a method that takes `void` and returns `String`. The type inference engine can deduce that it will work for an SMI rather takes `void` and returns anything `super String`. Do not confuse the two. – Boris the Spider Aug 19 '17 at 16:44
  • a lambda of the form `() -> String` can be a `Supplier ` or any other functional interface that takes `void` and returns a `String`, so it cannot have a specific predetermined type. – marsouf Aug 19 '17 at 16:51
  • Not sure what you mean. The type of a lambda and the type of the SMI it implements are not the same thing, that is what I am tying to tell you. Do not confuse 1) the type of the lambda 2) the type of the SMI and 3) the type inference system that matches them. Java is a very strongly typed language - everything always has a concrete type. What you are saying is equivalent to suggesting that `"Test"` does not have a type because it can be assigned to `String` or `Charsequence`. – Boris the Spider Aug 19 '17 at 16:55
  • @BoristheSpider While conceptually what you say is true and convincing, the type inference and mechanisms behind lambdas are quite bit more complicated than for "ordinary" objects. Particularly, a lambda expression "receives" a type that is deduced from the context where it is used. So the expression *itself* does not really "have" a type. Somehow related: https://stackoverflow.com/q/36336848/3182664 – Marco13 Aug 19 '17 at 18:01
  • 1
    @marsouf This is an oversight and has been corrected in java 9. See http://download.java.net/java/jdk9/docs/api/java/util/stream/Stream.html#generate-java.util.function.Supplier- – Misha Aug 19 '17 at 21:19
  • This is a very good observation. I remember that most Stream methods use generics like `? super T` or `? extends T` rather than just `T`. So I thought that this must be an oversight or something. And [yes, it is](https://stackoverflow.com/a/45776459/507738). – MC Emperor Sep 15 '22 at 06:56

2 Answers2

10

It's a bug. See https://bugs.openjdk.java.net/browse/JDK-8132097

It has been corrected in java 9. As you can see here, the method declaration is now

static <T> Stream<T> generate​(Supplier<? extends T> s)
Misha
  • 27,433
  • 6
  • 62
  • 78
0

Best perhaps to demonstrate with a different example.

Given a Supplier for, let's say, a double:

Supplier<Double> generator = Math::random;

We can, of course, create a Stream of doubles with this:

Stream<Double> ds = Stream.generate(generator);

But what if I wanted to have a Stream of numbers? After all, given that a double is a number, it only makes sense to be able to allow something like this:

Stream<Number> ns = Stream.generate(generator);

The latter of your two signatures would not allow the above statement to compile, as generator is not a Supplier<Number>. It is, though, a Supplier<Double>, and thus a Supplier<? extends Number>.

Joe C
  • 15,324
  • 8
  • 38
  • 50