Java8 - How does explicit type matches one variant - not other type?
Asked Answered
L

3

2

I have a simple snippet as below. I referred this

List<Document> list = new LinkedList<Document>();
FindIterable<Document> itr = collection.find(findQuery)
                       .forEach((Document doc) -> list.add(doc));
return list;

It compiles without any issues.

  1. I guess that we are telling compiler that doc is of type Document. Why is it needed?

But If I do the below, it throws ambiguous error. I referred this But couldn't relate and understand exactly.

collection.find(findQuery).forEach(list::add);
  1. Could anyone please explain why second statement is not working?

  2. is there any better way of writing the first one [working one]?

Java version: 1.8.0_231

import statements:

import java.util.List;
import java.util.Optional;
import com.mongodb.client.FindIterable;
import org.bson.Document;
Louralourdes answered 22/4, 2020 at 7:46 Comment(4)
@HadiJ Thanks. Corrected that. I got the same. I updated also.Louralourdes
Which java version and client version are you using and can you mention the proper imports of the class mentioned in the code? the forEach doesn't compile for me. unable to reproduce this.Necrosis
The title could be easily a duplicate of this Q&A, it would make much sense to edit it and point to the problem that you are actually dealing with. @Louralourdes are you also using an eclipse compiler?Necrosis
@Necrosis I edited. Yes, I compile it via eclipseLouralourdes
L
1

FindIterable inherits two forEach methods:

You could rewrite your paramter with either

Consumer<Document> consumer = documents::add;
Block<Document> block = list::add;

And either will work. These too will work:

.forEach((Consumer<Document>) doc -> list.add(doc))
.forEach((Consumer<Document>) list::add);

However, when you call forEach(list::add) or forEach(doc -> list.add(doc)), the compiler is unable to pick which overload will determine the method reference's target type (because the expression is compatible with both in that context).

Now, I'm not 100% sure why .forEach((Document doc) -> list.add(doc)) successfully selects/links the signature of Consumer<Document> instead of the one with Block<? super Document>, I'm surmizing it has to do with the generic bounds (but I'm still reading on this).

The choice for you should be easy because the Block<? super Document> version is deprecated.

Luge answered 22/4, 2020 at 8:12 Comment(3)
Thanks for clarifying my core doubt. Does (Consumer<Document>) mean use Consumer variant? And as you mentioned I am also trying to figure out how it matches the consumer variant in my case.Louralourdes
.forEach((Document doc) -> list.add(doc)) does indeed match the signature taking a Consumer. As I noted, I'm still reading on why the compiler picks Consumer as target type in this case. I'll update in a moment.Luge
@Louralourdes I don't have an explanation for why .forEach((Document doc) -> list.add(doc)) works, so I posted this question: #61362325 . So you may want to check answers there if you're interested to know the explanation.Luge
B
2

The problem is that forEach is just a Consumer, which has a single method void accept(T element), and you're trying to return a value.

The "ambiguous" error in the first version was subject to other posts here.

You can do (I'd consider that more idiomatic)

return StreamSupport.stream(collection.find(findQuery).spliterator(), false)
                    .collect(Collectors.toList());
Bolection answered 22/4, 2020 at 7:51 Comment(4)
NO. .forEach(list::add); is not working. It throws ambiguous error mentioned in the first link of the questionLouralourdes
@Louralourdes You're right, my main point was why the second doesn't work. Removed it from my answer and linked to an existing explanation thread. The one I left in works and is better anyway.Bolection
And Can I make stream as parallel here? Thanks for providing me the better approach.Louralourdes
I recall the StreamSupport not being very friendly with AggregateItterable at least. Which mongo-java-client version are you using?Necrosis
L
1

FindIterable inherits two forEach methods:

You could rewrite your paramter with either

Consumer<Document> consumer = documents::add;
Block<Document> block = list::add;

And either will work. These too will work:

.forEach((Consumer<Document>) doc -> list.add(doc))
.forEach((Consumer<Document>) list::add);

However, when you call forEach(list::add) or forEach(doc -> list.add(doc)), the compiler is unable to pick which overload will determine the method reference's target type (because the expression is compatible with both in that context).

Now, I'm not 100% sure why .forEach((Document doc) -> list.add(doc)) successfully selects/links the signature of Consumer<Document> instead of the one with Block<? super Document>, I'm surmizing it has to do with the generic bounds (but I'm still reading on this).

The choice for you should be easy because the Block<? super Document> version is deprecated.

Luge answered 22/4, 2020 at 8:12 Comment(3)
Thanks for clarifying my core doubt. Does (Consumer<Document>) mean use Consumer variant? And as you mentioned I am also trying to figure out how it matches the consumer variant in my case.Louralourdes
.forEach((Document doc) -> list.add(doc)) does indeed match the signature taking a Consumer. As I noted, I'm still reading on why the compiler picks Consumer as target type in this case. I'll update in a moment.Luge
@Louralourdes I don't have an explanation for why .forEach((Document doc) -> list.add(doc)) works, so I posted this question: #61362325 . So you may want to check answers there if you're interested to know the explanation.Luge
P
0

As it is explicited in the lamba target typing

the Java compiler uses the target type of the context or situation in which the lambda expression was found. It follows that you can only use lambda expressions in situations in which the Java compiler can determine a target type

In your second snippet, the compiler cannot determine the target type of the lambda. Why?

Because when you use Method Reference the JRE infers the method type arguments, which in this case are ambiguous (e.g method reference works well only if there is non-ambiguous inference)

The JRE don't know if your using :

com.mongodb.client.MongoIterable.forEach(Block<? super TResult>)

OR

java.lang.Iterable.forEach(Consumer<? super T>)

That's why your first snippet works. By casting your Object. You get rid of this ambiguity.

Poult answered 22/4, 2020 at 8:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.