Search code examples
javacompiler-errorsjava-streamfunctional-interface

Using Predicate method - "The target type of this expression must be a functional interface"


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?


Solution

  • What's the difference between String::isBlank and p?

    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 if T is a functional interface type (§9.8) and the expression is congruent with the function type of the ground target type derived from T.

    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.