Reputation: 1659
The third line will not compile, because The target type of this expression must be a functional interface
:
Predicate<String> p = String::isBlank;
List.of("").stream().filter(p.negate());
List.of("").stream().filter((String::isBlank).negate()); // compile error
Why not? What's the difference between String::isBlank
and p
?
Upvotes: 2
Views: 670
Reputation: 28988
Lambda expressions and method references have no type by itself. They are so-called poly expressions, it means the way they will be interpreted depends on the context in which they appear.
For instance, in the statement below, method references String::isBlank
conforms to the Consumer
interface. It will compile and execute successfully:
List.<String>of().forEach(String::isBlank);
And in the following statement, the type or reference String::isBlank
would be inferred as a Function<String>
:
Stream.<String>of().map(String::isBlank);
The compiler gets confused when you're trying to chain method on something that has no type by itself, it has no information to infer what to which functional interface String::isBlank
could conform in (String::isBlank).negate()
just from the fact that the overall expression expected to be a Predicate
. If you were expecting that method negate()
would give a clue to the compiler, then this assumption is wrong, it not the way how method resolution works in Java. Firstly, the compiler needs to know the type and only then it would be looking a potentially applicable method, among the methods that belong to this type, not the opposite.
You need to provide a context if you want to use it like that. I.e. method references should appear in either of these types of context: assignment context, invocation context, casting context.
Here is an example of how we can use invocation context, the following code compile:
List.of("").stream().filter(m(String::isBlank).negate());
public static <T> Predicate<T> m(Predicate<T> predicate) {
return predicate;
}
In this is an example of casting context already posted in the comments by @Jesper:
filter(((Predicate<? super String>) (String::isBlank)).negate())
Upvotes: 2
Reputation: 271660
What's the difference between
String::isBlank
andp
?
p
has a type. String::isBlank
does not.
From the Java Language Specification, Type of a Method Reference,
A method reference expression is compatible in an assignment context, invocation context, or casting context with a target type
T
ifT
is a functional interface type (§9.8) and the expression is congruent with the function type of the ground target type derived fromT
.
It then proceeds to define what "congruent" and "ground target type" means. The relevant part to your question is, String::isBlank
is not in an assignment context, invocation context, or casting context, when you write it like this:
(String::isBlank).negate()
You might think this counts as an invocation context, but invocation contexts are actually the arguments of method calls, like someMethod(String::isBlank)
. As the spec says:
Invocation contexts allow an argument value in a method or constructor invocation (§8.8.7.1, §15.9, §15.12) to be assigned to a corresponding formal parameter.
Anyway, because String::isBlank
is not in any of those contexts, the spec doesn't say anything about its type. In fact, just a bit up the page, it states,
It is a compile-time error if a method reference expression occurs in a program in someplace other than an assignment context (§5.2), an invocation context (§5.3), or a casting context (§5.5).
So those contexts turn out to be the only contexts that method references can occur in!
I didn't design the Java Language, so I can't tell you how the designers thought when they designed this, but I know that this design is rather simple to implement, compared to, say, allowing method references everywhere, which would require considering a lot more edge cases, and how this would interact with other language features. If method references are just limited to these three contexts, there won't be as much edge cases to consider, and it's relatively easy to figure out what types they are, and if you want to use them in a random expression somewhere, this design also allows you to do that just by casting:
((Predicate<String>)String::isBlank).negate()
It's a fairly good compromise, in my opinion.
If you are still wondering "why didn't they implement it like ... instead?" I recommend checking out Eric Lippert's answer here.
Upvotes: 4