On type inference
Predicate has default methods and, or and negate.
However, String::isEmpty is not a Predicate. It could also be Function<String, Boolean>. Without type inference, there is no way to know its functional interface.
If Java had filterNot method (hint: it doesn't), there could be a nice implicit inference:
s.filterNot(String::isEmpty).count()
Negation by instance method (as mentioned by @axtavt) involves explicit inference:
s.filter(((Predicate<String>) String::isEmpty).negate()).count()
It is rather unsightly. From code clarity point of view, implicit inference generally looks much better, even if it makes compiler's work a little more complicated.
Moreover, method reference isn't a unique identifier of a method. E.g. String::substring refers to both methods of the same name. Due to method overloading, it is unclear which method it refers to. This shows there is a lot of ambiguity that needs to be cleared.
Before we continue, look at these examples and figure out what happens (last two use Java 11):
Predicate<String> p1 = /*0*/ s -> s.isEmpty(); Function<String, Boolean> f1 = /*1*/ String::isEmpty; Object o1 = /*2*/ String::isEmpty; Object o2 = /*3*/ p1; Function<String, Boolean> f2 = /*4*/ (Function<String, Boolean>) o2; Function<String, Boolean> f3 = /*5*/ p1::test; Predicate<Integer> p2 = /*6*/ s -> s.isEmpty(); Predicate<Integer> p3 = /*7*/ String::isEmpty; var v1 = /*8*/ Predicate.not(String::isEmpty); var v2 = /*9*/ Predicate.not(s -> s.isEmpty());
Let's analyze these examples:
The lambda infers all its type information from p1 variable in assignment (=). That also infers that it's actually String s, and since String has instance method named isEmpty with no arguments and correct output type, it compiles.
The method reference also infers its type information from f1 variable in assignment. That also infers the arguments and their types. There is only one matching method in String.
The method reference fails to infer a functional interface from o1, Object is a class.
Just a simple (if perhaps silly) down-casting of a Predicate to Object o2.
The right operand of assignment is an expression that attempts to cast a Predicate that is stored in variable of Object type, as a Function f2. Exception is thrown at runtime.
The method reference refers to an instance method of p1. Functional interface and arguments of said method are inferred from f3 variable, and there is a match.
Effectively, this is a Function<String, Boolean> wrapper to a Predicate<String>.
The lambda infers the functional interface and arguments from p3. The compiler looks for an instance method called isEmpty with no arguments in Integer, but there is no such thing.
The method reference infers functional interface. It also infers Integer as input argument and Boolean as result type. This rules out instance methods of String (that would require first argument to be of type String rather than Integer). There is no static method String.isEmpty(Integer) either, so a compiler error occurs.
By using var, v1 is still strongly typed at compile time, but the inference in the assignment (=) goes from right to left this time.
The right operand calls static method not which has one argument, Predicate<T>. Method reference can infer input arguments count (1) and return type (boolean). One such method exists in String and its input type is String, so method not can infer String for <T>.
Similarly, v2 is a mere consumer here. However, the lack of type information in the lambda results in compiler error as it's unclear what is the type of s. Perhaps List<BigDecimal>?
Now that it's clear how it works, let's revisit Predicate.not. As others mentioned, it was added in Java 11. If you want a cleaner look, especially if you negate multiple predicates in the same class, you might as well go for static import and have a result like this:
s.filter(not(String::isEmpty))
The static import however has its own downside: Adding another not method that negates something vaguely similar will result in ambiguity that you will need to resolve for your code to compile. The ambiguity is further aggravated by type erasure.
So, it all comes back to the fan favorite, a lambda negated by ! operator. This allows the lambda to infer its type directly from the filter method and it doesn't look too bad.
s.filter(s -> !s.isEmpty)
(unless you've developed in some newer language like Scala, Kotlin ...)
Summed up, functional paradigm in Java could look and feel better, so pick your poison.
Predicate.not(Predicate)method. But that issue is still open so we'll see this at the earliest in Java 12 (if ever).