0

Consider the following snippet:

Stream.of(stringList, Arrays.asList(1))
    .map(list -> list)
    .reduce((list1, list2) -> {list1.addAll(list2); return list1;});

As far as I can tell, this should not compile because the Stream<? extends List<? extends Whatever>> type being inferred after map() takes the Capture Conversion to a restricted type that is applicable for only 1 execution and when it reaches the reduce() function - it should change to different restricted types that are unrelated.

Calling list1.addAll(list2) should thus not compile.

I'm not sure how the Type Inference rules internally work for this. I would like to understand the detailed steps regarding the inference procedure for this particular case.

Note that it does not compile without the map(list -> list).


Edit: based on comments of @Didier L, I would like to clarify what I mean by applicable for only 1 execution in the above question based on my understanding-:

  • the capture conversion converts the wildcard to a non-representable type only for one single expression evaluation
  • even if the variable is same - but used somewhere else in the program - the type being assumed for the next time will be different from what it was assumed before.
  • if this is loop - then the capture conversion runs only for the scope of the loop - and should not escape out of the loop. Consider this code:
List<String> stringLists = new ArrayList<>();
List<?> list = stringLists;
list.add(list.get(9)); // this will fail
  • here list.get(9) will be capture converted to the type X
  • but the list.add(...) may have the type Y - as assumed by another capture conversion of <?> by the compiler
  • so the addition of X to type Y is not allowed because of above restriction.
Didier L
  • 18,905
  • 10
  • 61
  • 103
theutonium.18
  • 483
  • 2
  • 7
  • For this specific case, use `Stream.concat`? – Andy Turner Feb 16 '22 at 09:28
  • I also don't know what you'd expect here. Isn't the point that if you're looking to join the lists, you can't expect the `.map(list -> list)` to cast to a common type other that `List>` (or `List extends Serializable>`), because `List` and `List` aren't related to, say, `List`. Does it do what you expect if you use `.map(ArrayList::new)`? – Andy Turner Feb 16 '22 at 09:30
  • The capture conversion is supposed to produce `List` and `List` as the 2 distinct types - and then doing `List.addAll(Collection extends E> c)` should error out as X and Y are unrelated - But what I presume is that the above operation is converting both the lists to a single type List and List where K is the capture conversion of `? extends Serializable`. – theutonium.18 Feb 16 '22 at 09:40
  • 1
    @AndyTurner : This process seems incorrect - as ideally X and Y are unrelated and should not be converted to the same types. – theutonium.18 Feb 16 '22 at 09:42
  • @AndyTurner : What I mean is that `func(List>,List>)` will get capture converted to `func(List, List)` and not `func(List,List)` – theutonium.18 Feb 16 '22 at 09:44
  • 1
    I agree that for whatever `List extends X>` that the compiler determines, it shouldn’t accept to call `addAll()`. I’d be curious to understand why this compiles, especially considering that compilation fails if you remove the `map(list -> list)`. – Didier L Feb 16 '22 at 09:53
  • I tried to clarify the question a bit but although the general meaning seems clear, I’m not sure of exactly what you mean with “_applicable for only 1 execution […]_” and the rest of the sentence. (Maybe this question should reason about [covariance and contravariance](https://stackoverflow.com/q/8481301/525036)). – Didier L Feb 16 '22 at 10:31
  • Side note, I did some more tests with Eclipse (ecj), which accepts this as well. Extracting the `map()` result to a local variable will cause a compilation failure with both javac and ecj, ever when using `var` (although IntelliJ will not show an error, make sure to trigger a compilation if using it). I also noticed that the Eclipse IDE itself generates an error when hovering `list1` or `list2` (shown in the _Error Log_ view). – Didier L Feb 18 '22 at 13:07
  • @DidierL : have elaborated my understanding on "only 1 execution". – theutonium.18 Feb 18 '22 at 13:58
  • @AndyTurner: have tried to edit my question - am I able to make my point this time? My question is around the detailed algorithm being followed by compiler for Type Inference in this particular case. – theutonium.18 Feb 18 '22 at 14:07
  • Yep, your point is what is described in [What is a capture conversion in Java (…)?](https://stackoverflow.com/a/4432035/525036). Note that in this specific case the problem would be with the lambda implementing `BinaryOperator`, where `T` is used as the type of the 2 arguments of the lambda. This means those arguments have the same bounds, which is I believe the crux of the issue: the compiler infers the same capture variable when it shouldn’t. I didn’t find a way to make it print the actual types when the compilation succeeds though. – Didier L Feb 18 '22 at 17:03
  • I just add a kind of revelation during the night: if you implement a `class ListBinaryOperator implements BinaryOperator>` that does the same as the lambda, that class should compile, right? And then, what should prevent using it as `reduce(new ListBinaryOperator<>())` operation? However, why does the compiler still reject it without the `map()`? – Didier L Feb 23 '22 at 09:47

0 Answers0