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
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With