如何否定方法引用谓词


330

在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); }

6
JDK-8050818涵盖了静态Predicate.not(Predicate)方法的添加。但是这个问题仍然存在,因此我们最早会在Java 12中看到它(如果有的话)。
Stefan Zobel,

1
似乎此答案也可能是JDK / 11中采用的最终解决方案。
纳曼

2
我真的很想看看这种情况下的特殊方法引用语法:s.filter(String ::!isEmpty)
Mike Twain

Answers:



214

我打算静态导入以下内容,以允许将方法引用内联使用:

public static <T> Predicate<T> not(Predicate<T> t) {
    return t.negate();
}

例如

Stream<String> s = ...;
long nonEmptyStrings = s.filter(not(String::isEmpty)).count();

更新:从Java-11开始,JDK还提供了内置的类似解决方案


9
@SaintHill,但是您必须将其写出,并给参数指定名称
flup



149

有一种方法可以构成与当前方法参考相反的方法参考。请参见下面的@vlasec答案,该答案显示了如何通过将方法引用显式转换为a Predicate,然后使用该negate函数进行转换来实现。这是其他一些不太麻烦的方法之一。

相反的:

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();

就我个人而言,我更喜欢后一种技术,因为我发现它it -> !it.isEmpty()比冗长而冗长的显式强制转换然后取反更清晰。

一个人也可以做一个谓词并重用它:

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:Streams的性能vs收藏

**我因暗示for循环技术更快而感到惊讶。它消除了流的创建,消除了使用另一个方法调用(谓词的负函数),并且消除了临时累加器列表/计数器。因此,最后一个构造保存的一些内容可能会使它更快。

我确实认为它更简单,更好,即使不是更快。如果工作需要锤子和钉子,请不要带电锯和胶水!我知道有些人对此表示怀疑。

wish-list:Stream随着Java用户更加熟悉Java 功能,我希望Java 功能有所发展。例如,Stream中的'count'方法可以接受a,Predicate以便可以像这样直接完成:

Stream<String> s = ...;
int notEmptyStrings = s.count(it -> !it.isEmpty());

or

List<String> list = ...;
int notEmptyStrings = lists.count(it -> !it.isEmpty());

您为什么说它快很多
何塞·安迪亚斯(JoséAndias)'16

@JoséAndias(1)是更快还是“快得多”?(2)如果是,为什么?你确定了什么?
协调员

3
我想请您详细说明“运行速度快得多”。问题:(1)是更快还是“快得多”?(2)如果是,为什么?你确定了什么?声明的作者更好地回答了您。我不认为它会更快或更慢。谢谢
何塞·安迪亚斯(JoséAndias)'16

2
然后,我将其排除在外,以供您考虑-它消除了流的创建,消除了使用另一个方法调用(谓词的负函数),并且消除了临时的累加器列表/计数器。因此,最后一个构造保存了一些内容。我不确定是更快还是快多少,但我认为它“快很多”。但也许“很多”是主观的。较晚编写代码要比使否定谓词和流更直接进行计数。我的偏爱。
协调员

4
negate()似乎是理想的解决方案。可惜它不是静态的,Predicate.negate(String::isEmpty);没有繁琐的转换。
乔尔·谢姆托夫

91

Predicate有一些方法andornegate

但是,String::isEmpty不是Predicate,它只是一个String -> Booleanlambda,它仍然可以变成任何东西,例如Function<String, Boolean>类型推断是首先需要发生的事情。该filter方法隐式推断类型。但是,如果您在将其作为参数传递之前对其进行否定,它将不再发生。正如@axtavt所提到的,显式推断可以用作一种丑陋的方式:

s.filter(((Predicate<String>) String::isEmpty).negate()).count()

在其他答案中还建议了其他not方法,其中静态方法和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无法编译-lambdas需要推断一个功能接口(=使用一种抽象方法)
  • p1和f1正常工作,分别推断出不同的类型
  • obj2转换PredicateObject-愚蠢但有效
  • f2在运行时失败-您不能转换PredicateFunction,它不再与推理有关
  • f3有效-您调用test由其lambda定义的谓词方法
  • p2无法编译- Integer没有isEmpty方法
  • p3也不编译-没有String::isEmptyInteger参数的静态方法

我希望这有助于对类型推断的工作方式有更多的了解。


45

基于他人的答案和个人经验:

Predicate<String> blank = String::isEmpty;
content.stream()
       .filter(blank.negate())

4
有趣的是–您不能::像希望的那样内联函数引用String::isEmpty.negate(),但是如果您先分配一个变量(或先转换为变量Predicate<String>),那行得通。我认为带lambda w /的!大多数情况下可读性最高,但是了解什么可以编译和不编译是有帮助的。
约书亚·戈德堡

2
@JoshuaGoldberg我在回答中解释说:方法引用本身不是谓词。在此,转换是通过变量完成的。
Vlasec

17

另一种选择是在无歧义的上下文中利用lambda强制转换到一个类中:

public static class Lambdas {
    public static <T> Predicate<T> as(Predicate<T> predicate){
        return predicate;
    }

    public static <T> Consumer<T> as(Consumer<T> consumer){
        return consumer;
    }

    public static <T> Supplier<T> as(Supplier<T> supplier){
        return supplier;
    }

    public static <T, R> Function<T, R> as(Function<T, R> function){
        return function;
    }

}

...然后静态导入实用程序类:

stream.filter(as(String::isEmpty).negate())

1
我实际上对此工作感到惊讶-但似乎JDK比函数<T,Boolean>更喜欢Predicate <T>。但是,您不会让Lambdas将任何内容强制转换为Function <T,Boolean>。
Vlasec

它适用于字符串,但不适用于列表:错误:(20,39)java:引用as模棱两可是com.strands.sbs.function中的方法<T> as(java.util.function.Consumer <T>)。 Lambdas和com.strands.sbs.function.Lambdas匹配中的方法<T,R> as(java.util.function.Function <T,R>)
Daniel Pinyol

Daniel,如果您尝试使用重载方法,可能会发生:)
Askar Kalykov

既然我比原先更好地理解了类型推断,那么我了解了它的工作原理。基本上,它只是找到唯一可行的选项。看起来很有趣,我只是不知道是否有一些更好的名称不会引起重复。
Vlasec

12

Predicate#negate应该是您要找的东西吗?


您需要先获得Predicate
Sotirios Delimanolis,2014年

21
您必须强制String::isEmpty()转换Predicate<String>-非常丑陋。
axtavt 2014年

3
@assylias用作Predicate<String> p = (Predicate<String>) String::isEmpty;p.negate()
Sotirios Delimanolis,2014年

8
我知道@SotiriosDelimanolis,但这没有达到目的- s -> !s.isEmpty()在这种情况下,我宁愿写!
assylias 2014年

@assylias:是的,我相信实际上是这个主意。仅仅写出lambda速记就是预期的后备。
Louis Wasserman 2014年

8

在这种情况下,您可以使用org.apache.commons.lang3.StringUtils和做

int nonEmptyStrings = s.filter(StringUtils::isNotEmpty).count();

6
不。问题是如何取反任何方法引用,并以String::isEmpty一个示例为例。如果您有此用例,它仍然是相关信息,但是,如果仅回答String用例,则不应接受。
安东尼·德罗贡

4

我编写了一个完整的实用程序类(受Askar的建议启发),该类可以接受Java 8 lambda表达式,并将它们(如果适用)转换为该包中定义的任何类型化的标准Java 8 lambda java.util.function。例如,您可以执行以下操作:

  • asPredicate(String::isEmpty).negate()
  • asBiPredicate(String::equals).negate()

因为如果所有静态方法都命名为just 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...
}

4

您可以使用谓词Eclipse的收藏

MutableList<String> strings = Lists.mutable.empty();
int nonEmptyStrings = strings.count(Predicates.not(String::isEmpty));

如果您无法从更改字符串List

List<String> strings = new ArrayList<>();
int nonEmptyStrings = ListAdapter.adapt(strings).count(Predicates.not(String::isEmpty));

如果只需要否定项,String.isEmpty()也可以使用StringPredicates.notEmpty()

注意:我是Eclipse Collections的撰稿人。



0

如果您使用的是Spring Boot(2.0.0+),则可以使用:

import org.springframework.util.StringUtils;

...
.filter(StringUtils::hasLength)
...

哪个: return (str != null && !str.isEmpty());

因此它将具有所需的求反作用 isEmpty

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.