在Java 8中,你可以使用方法引用来过滤流,例如:
Stream<String> s = ...;
long emptyStrings = s.filter(String::isEmpty).count();
是否有一种方法可以创建一个方法引用,它是现有方法引用的否定,例如:
long nonEmptyStrings = s.filter(not(String::isEmpty)).count();
我可以创建如下所示的not方法,但我想知道JDK是否提供了类似的东西。
static <T> Predicate<T> not(Predicate<T> p) { return o -> !p.test(o); }
谓词有方法和,或和否定。
然而,String::isEmpty不是一个谓词,它只是一个String ->布尔lambda,它仍然可以成为任何东西,例如Function<String,布尔>。首先需要进行类型推断。filter方法隐式地推断类型。但如果你在传递它作为参数之前对它求反,它就不再发生了。正如@axtavt提到的,显式推理可以被用作一种丑陋的方式:
s.filter(((Predicate<String>) String::isEmpty).negate()).count()
在其他答案中还有其他建议,静态而不是方法和lambda最有可能是最好的想法。tl和dr部分到此结束。
但是,如果您想更深入地理解lambda类型推断,我想通过示例更深入地解释它。看看这些,看看会发生什么:
Object obj1 = String::isEmpty;
Predicate<String> p1 = s -> s.isEmpty();
Function<String, Boolean> f1 = String::isEmpty;
Object obj2 = p1;
Function<String, Boolean> f2 = (Function<String, Boolean>) obj2;
Function<String, Boolean> f3 = p1::test;
Predicate<Integer> p2 = s -> s.isEmpty();
Predicate<Integer> p3 = String::isEmpty;
obj1 doesn't compile - lambdas need to infer a functional interface (= with one abstract method)
p1 and f1 work just fine, each inferring a different type
obj2 casts a Predicate to Object - silly but valid
f2 fails at runtime - you cannot cast Predicate to Function, it's no longer about inference
f3 works - you call the predicate's method test that is defined by its lambda
p2 doesn't compile - Integer doesn't have isEmpty method
p3 doesn't compile either - there is no String::isEmpty static method with Integer argument
我已经编写了一个完整的实用程序类(受到Askar的建议的启发),它可以接受Java 8 lambda表达式,并将它们(如果适用)转换为包Java .util.function中定义的任何类型的标准Java 8 lambda。你可以这样做:
asPredicate(String::isEmpty).negate()
asBiPredicate(String::equals).negate()
因为如果所有静态方法都以as()命名,将会产生许多歧义,所以我选择调用方法“as”,后面跟着返回的类型。这使我们能够完全控制lambda解释。下面是实用工具类的第一部分(有点大),揭示了所使用的模式。
在这里查看完整的课程(要点)。
public class FunctionCastUtil {
public static <T, U> BiConsumer<T, U> asBiConsumer(BiConsumer<T, U> biConsumer) {
return biConsumer;
}
public static <T, U, R> BiFunction<T, U, R> asBiFunction(BiFunction<T, U, R> biFunction) {
return biFunction;
}
public static <T> BinaryOperator<T> asBinaryOperator(BinaryOperator<T> binaryOperator) {
return binaryOperator;
}
... and so on...
}
有一种方法可以组合与当前方法引用相反的方法引用。参见下面@vlasec的回答,该回答展示了如何显式地将方法引用转换为Predicate,然后使用否定函数进行转换。这是其他几种不太麻烦的方法中的一种。
反义词:
Stream<String> s = ...;
int emptyStrings = s.filter(String::isEmpty).count();
是这样的:
Stream<String> s = ...;
int notEmptyStrings = s.filter(((Predicate<String>) String::isEmpty).negate()).count()
或:
Stream<String> s = ...;
int notEmptyStrings = s.filter( it -> !it.isEmpty() ).count();
就我个人而言,我更喜欢后一种技术,因为我发现它比冗长的显式强制转换然后求反更清晰。
还可以创建一个谓词并重用它:
Predicate<String> notEmpty = (String it) -> !it.isEmpty();
Stream<String> s = ...;
int notEmptyStrings = s.filter(notEmpty).count();
或者,如果有一个集合或数组,只需使用简单的for循环,开销更少,并且*可能更快:
int notEmpty = 0;
for(String s : list) if(!s.isEmpty()) notEmpty++;
*如果你想知道哪个更快,那么使用JMH http://openjdk.java.net/projects/code-tools/jmh,避免手动基准代码,除非它避免所有JVM优化-参见Java 8:流与集合的性能
**我因为建议for循环技术更快而受到批评。它消除了流的创建,它消除了使用另一个方法调用(谓词为负函数),并且它消除了临时累加器列表/计数器。最后一个构造保留的一些东西可能会让它更快。
我确实认为它更简单、更好,即使不是更快。如果这项工作需要锤子和钉子,不要带电锯和胶水!我知道你们有些人对此有异议。
愿望清单:我希望看到Java流函数的发展,现在Java用户更熟悉它们了。例如,Stream中的'count'方法可以接受Predicate,这样就可以像这样直接完成:
Stream<String> s = ...;
int notEmptyStrings = s.count(it -> !it.isEmpty());
or
List<String> list = ...;
int notEmptyStrings = lists.count(it -> !it.isEmpty());