Reputation: 628
I have a list of names. On line 3, I had to cast the result of the lambda expression to Predicate<String>
. The book I'm reading explains that the cast is necessary in order to help the compiler determine what the matching functional interface is.
However, I don't need such a cast on the following line because I don't call negate()
. How does this make any difference? I understand that negate()
here returns Predicate<String>
, but does the preceding lambda expression not do the same?
List<String> names = new ArrayList<>();
//add various names here
names.removeIf(((Predicate<String>) str -> str.length() <= 5).negate()); //cast required
names.removeIf(((str -> str.length() <= 5))); //compiles without cast
Upvotes: 7
Views: 878
Reputation: 255
Without getting into things too deeply, the bottom line is that the lambda str -> str.length() <= 5
is not necessarily a Predicate<String>
as Eugene explained.
That said, negate()
is a member function of Predicate<T>
and the compiler isn't able to use the call to negate()
to help with inferring the type that the lambda should be interpreted as. Even if it tried there might be issues because if multiple possible classes had negate()
functions it wouldn't know which to choose.
Imagine that you're the compiler.
str -> str.length() <= 5
. You know that it is lambda that takes a String and returns a boolean but don't know specifically which type it represents..negate()
but because you don't know the type of the expression to the left of the "." you need to report an error since you can't use the call to negate to help infer the type.Had negate been implemented as a static function then things would work. For example:
public class PredicateUtils {
public static <T> Predicate<T> negate(Predicate<T> p) {
return p.negate();
}
would enable you to write
names.removeIf(PredicateUtils.negate(str -> str.length() <= 5)); //compiles without cast
Because the choice of negate function is unambiguous the compiler knows that it needs to interpret str -> str.length() <= 5
as a Predicate<T>
and can coerce the type properly.
I hope that this helps.
Upvotes: 1
Reputation: 40047
In the following example
names.removeIf(str -> str.length() <= 5); //compiles without cast
the expression returns true. If in the following example without the cast, true
knows nothing about the method negate()
On the other hand,
names.removeIf(((Predicate<String>) str -> str.length() <= 5).negate()); //cast required
The expression is cast to Predicate<String>
to tell the compiler where to find the method negate
. Then it's the method negate()
that actually does the evaulation by the following:
(s)->!test(s) where s is the string argument
Note that you could achieve the same result without the cast as follows:
names.removeIf(str->!str.length <= 5)
// or
name.removeIf(str->str.length > 5)
Upvotes: 1
Reputation: 120998
I do not know why this has to be so confusing. This can be explained with 2 reasons, IMO.
I will let you figure out what this means and what the JLS
words are around it. But in essence these are just like generics:
static class Me<T> {
T t...
}
what is the type T
here? Well, it depends. If you do :
Me<Integer> me = new Me<>(); // it's Integer
Me<String> m2 = new Me<>(); // it's String
poly expressions are said that they depend on the context of where they are used. Lambda expressions are the same. Let's take the lambda expression in isolation here:
(String str) -> str.length() <= 5
when you look at it, what is this? Well it's a Predicate<String>
? But may be A Function<String, Boolean>
? Or may be even MyTransformer<String, Boolean>
, where:
interface MyTransformer<String, Boolean> {
Boolean transform(String in){
// do something here with "in"
}
}
The choices are endless.
.negate()
called directly could be an option.From 10_000 miles above, you are correct: you are providing that str -> str.length() <= 5
to a removeIf
method, that only accepts a Predicate
. There are no more removeIf
methods, so the compiler should be able to "do the correct thing" when you supply that (str -> str.length() <= 5).negate()
.
So how come this does not work? Let's start with your comment:
Shouldn't the call to negate() have provided even more context, making the explicit cast even less necessary?
It seems this is where the main problem starts with, this is simply not how javac
works. It can't take the entire str -> str.length() <= 5).negate()
, tell itself that this is a Predicate<String>
(since you are using it as an argument to removeIf
) and then decompose further the part without .negate()
and see if that is a Predicate<String>
also. javac
acts in reverse, it needs to know the target in order to be able to tell if it is legal to call negate
or not.
Also you need to make a clear distinction between poly expressions and expressions, in general. str -> str.length() <= 5).negate()
is an expression, str -> str.length() <= 5
is a poly expression.
There might be languages where things are done differently and where this is possible, javac
is simply not that type.
Upvotes: 4
Reputation: 45339
It is not quite just because you call negate()
. Take a look at this version, which is very close to yours but does compile:
Predicate<String> predicate = str -> str.length() <= 5;
names.removeIf(predicate.negate());
The difference between this and your version? It's about how lambda expressions get their types (the "target type").
What do you think this does?
(str -> str.length() <= 5).negate()?
Your current answer is that "it calls negate()
on the Predicate<String>
given by the expression str -> str.length() <= 5
". Right? But that's just because this is what you meant it to do. The compiler doesn't know that. Why? Because it could be anything. My own answer to the above question could be "it calls negate
on my other functional interface type... (yes, the example will be a little bizarre)
interface SentenceExpression {
boolean checkGrammar();
default SentenceExpression negate() {
return ArtificialIntelligence.contradict(explainSentence());
};
}
I could use the very same lambda expression names.removeIf((str -> str.length() <= 5).negate());
but meaning str -> str.length() <= 5
to be a SentenceExpression
rather than a Predicate<String>
.
Explanation: (str -> str.length() <= 5).negate()
does not make str -> str.length() <= 5
a Predicate<String>
. And this is why I said it could be anything, including my functional interface above.
Back to Java... This is why lambda expressions have the concept of "target type", which defines the mechanics by which a lambda expression is understood by the compiler as of a given functional interface type (i.e., how you help the compiler know that the expression is a Predicate<String>
rather than SentenceExpression
or anything else it could be). You may find it useful to read through What is meant by lambda target type and target type context in Java? and Java 8: Target typing
One of the contexts in which target types are inferred (if you read the answers on those posts) is the invocation context, where you pass a lambda expression as the argument for a parameter of a functional interface type, and that is what is applicable to names.removeIf(((str -> str.length() <= 5)));
: it's just the lambda expression given as argument to a method that takes a Predicate<String>
. This does not apply to the statement that is not compiling.
So, in other words...
names.removeIf(str -> str.length() <= 5);
uses a lambda expression in a place where the argument type clearly defines what the type of the lambda expression is expected to be (i.e., the target type of str -> str.length() <= 5
is clearly Predicate<String>
).
However, (str -> str.length() <= 5).negate()
is not a lambda expression, it's just an expression that happens to use a lambda expression. This is to say that str -> str.length() <= 5
in this case is not in the invocation context that determines the lambda expression's target type (as in the case of your last statement). Yes, the compiler knows that removeIf
needs a Predicate<String>
, and it knows for sure that the entire expression passed to the method has to be a Predicate<String>
, but it wouldn't assume that any lambda expression in the argument expression would be a Predicate<String>
(even if you treat it as a predicate by calling negate()
on it; it could have been anything that is compatible with the lambda expression).
That's why typing your lambda with an explicit cast (or otherwise, as in the first counter-example I gave) is needed.
Upvotes: 8