Background: I recently wrote an answer where I suggested writing the following code:
Files.write(Paths.get("PostgradStudent.csv"),
Arrays.stream(PGstudentArray).map(Object::toString).collect(Collectors.toList()),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
After some thoughts, I went: "I don't actually need a list here, I just need an Iterable<? extends CharSequence>
".
As Stream<T>
has a method Iterator<T> iterator()
, I then thought, well, that's easy:
Iterable<? extends CharSequence> iterable = () -> Arrays.stream(arr).map(Object::toString).iterator();
(I extracted it to a local variable for this question, I would like to do it inline at the end.) Unfortunately this does not compile without additional type hints:
error: incompatible types: bad return type in lambda expression
Iterable<? extends CharSequence> iterable = () -> Arrays.stream(arr).map(Object::toString).iterator();
^
Iterator<String> cannot be converted to Iterator<CharSequence>
Of course adding some type hints will make this work:
Iterable<? extends CharSequence> iterable2 = (Iterable<String>) () -> Arrays.stream(arr).map(Object::toString).iterator();
Iterable<? extends CharSequence> iterable3 = () -> Arrays.stream(arr).<CharSequence>map(Object::toString).iterator();
In my understanding, the Java compiler does the following things:
- It looks at the target type of the expression, which is
Iterable<? extends CharSequence>
. - It then determines the function type of this interface, which is
() -> Iterator<? extends CharSequence>
in my case. - It then looks at the lambda and checks whether it is compatible.
In my case, the lambda has a type of
() -> Iterator<String>
. Which is compatible with the function type determined in step 2.
Interestingly, if I change the target of the lambda to Supplier
:
Supplier<Iterator<? extends CharSequence>> supplier = () -> Arrays.stream(arr)
.map(Object::toString)
.iterator();
it will compile fine.
Why can't javac infer the correct type for this lambda?