如何否定方法引用谓词

Posted

技术标签:

【中文标题】如何否定方法引用谓词【英文标题】:How to negate a method reference predicate 【发布时间】:2014-02-24 14:51:33 【问题描述】:

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

【问题讨论】:

JDK-8050818 涵盖了静态Predicate.not(Predicate) 方法的添加。但是这个问题仍然存在,所以我们最早会在 Java 12 中看到这个问题(如果有的话)。 似乎this answer 也可能是 JDK/11 中的终极解决方案。 我真的很想看看这个案例的特殊方法参考语法:s.filter(String::!isEmpty) 【参考方案1】:

Predicate#negate 不应该是你要找的吗?

【讨论】:

您需要先获得Predicate 您必须先将String::isEmpty() 转换为Predicate&lt;String&gt; - 这非常难看。 @assylias 用作Predicate&lt;String&gt; p = (Predicate&lt;String&gt;) String::isEmpty;p.negate() @SotiriosDelimanolis 我知道,但这违背了目的 - 在这种情况下,我宁愿写 s -&gt; !s.isEmpty() @assylias:是的,我相信这实际上是这个想法;只是写出 lambda 长手是预期的后备。【参考方案2】:

有一种方法可以构成与当前方法引用相反的方法引用。请参阅下面的@vlasec 的答案,该答案显示了如何将方法引用显式转换为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 -&gt; !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: performance of Streams vs Collections

**我因为建议 for-loop 技术更快而受到抨击。它消除了流创建,消除了使用另一个方法调用(谓词的否定函数),并消除了临时累加器列表/计数器。因此,最后一个构造保存的一些东西可能会使其更快。

我确实认为它更简单更好,即使不是更快。如果工作需要锤子和钉子,不要带电锯和胶水!我知道你们中的一些人对此有异议。

wish-list:我希望看到 Java 的 Stream 函数随着 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());

【讨论】:

为什么说它快很多 @JoséAndias (1) 是更快还是“快得多”? (2) 如果有,为什么?你确定了什么? 我要求您详细说明“运行速度快得多”。问题:(1)它更快还是“快得多”? (2) 如果有,为什么?你确定了什么?最好由声明的作者来回答。我不认为它更快或更慢。谢谢 那么我将把它扔掉以供您考虑 - 它消除了流创建,消除了使用另一个方法调用(谓词的负函数),它消除了临时累加器列表/计数器。所以最后一个构造保存了一些东西。我不确定它是否更快或更快,但我认为它“快很多”。但也许“很多”是主观的。编写后面的代码比制作否定谓词和流来进行直接计数更简单。我的偏好。 negate() 似乎是一个理想的解决方案。可惜它不像Predicate.negate(String::isEmpty);那样是静态的,没有繁琐的转换。【参考方案3】:

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

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 也提供了 similar solution 内置功能。

【讨论】:

@SaintHill 但是你必须把它写出来,给参数一个名字 在番石榴docs.guava-libraries.googlecode.com/git/javadoc/com/google/… 更新番石榴链接:static.javadoc.io/com.google.guava/guava/23.0/com/google/common/…【参考方案4】:

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

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

【讨论】:

有趣——你不能像人们希望的那样内联函数 :: 引用(String::isEmpty.negate()),但如果你先分配给一个变量(或先转换为Predicate&lt;String&gt;),那作品。我认为 lambda w/! 在大多数情况下是最易读的,但了解什么可以编译和什么不能编译会很有帮助。 @JoshuaGoldberg 我在回答中解释说:方法引用本身不是谓词。在这里,转换是由变量完成的。【参考方案5】:

另一种选择是在非模棱两可的上下文中利用 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())

【讨论】:

我真的很惊讶它的工作原理——但似乎 JDK 更喜欢 Predicate 而不是 Function。但是您不会让 Lambda 将任何内容转换为 Function 它适用于 String 但不适用于 List: Error:(20, 39) java: reference to as is ambiguous both method as(java.util.function.Consumer) in com.strands.sbs.function.Lambdas 和 com.strands.sbs.function.Lambdas 中的方法 as(java.util.function.Function) 匹配 Daniel,如果您尝试使用重载方法,可能会发生这种情况:) 现在我比原来更好地理解了类型推断,我理解了它是如何工作的。基本上,它只是找到唯一有效的选项。看起来很有趣,只是不知道有没有更好的名字不会引起样板。【参考方案6】:

Predicate 有方法andornegate

但是,String::isEmpty 不是 Predicate,它只是一个 String -&gt; Boolean lambda,它仍然可以变成任何东西,例如Function&lt;String, Boolean&gt;类型推断是首先需要发生的事情。 filter 方法隐式推断类型。但是如果你在将它作为参数传递之前否定它,它就不再发生了。正如@axtavt 提到的,explicit 推理可以用作一种丑陋的方式:

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 无法编译 - lambda 需要推断功能接口(= 使用一个抽象方法) p1 和 f1 工作正常,每个都推断出不同的类型 obj2 将 Predicate 转换为 Object - 愚蠢但有效 f2 在运行时失败 - 您不能将 Predicate 转换为 Function,它不再是关于推理的 f3 有效 - 您调用谓词的方法 test 由其 lambda 定义 p2 无法编译 - Integer 没有 isEmpty 方法 p3 也无法编译 - 没有带有 Integer 参数的 String::isEmpty 静态方法

我希望这有助于更深入地了解类型推断的工作原理。

【讨论】:

【参考方案7】:

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

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

因为如果所有的静态方法都被命名为as() 会有很多歧义,所以我选择调用方法“as”,后跟返回的类型。这使我们可以完全控制 lambda 解释。下面是(有点大的)实用程序类的第一部分,揭示了所使用的模式。

看看complete class here(要点)。

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...

【讨论】:

【参考方案8】:

你可以从Eclipse Collections使用Predicates

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 的贡献者。

【讨论】:

【参考方案9】:

在这种情况下,你可以使用org.apache.commons.lang3.StringUtils然后做

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

【讨论】:

没有。问题是如何否定任何方法引用,并以String::isEmpty 为例。如果你有这个用例,它仍然是相关信息,但如果它只回答字符串用例,那么它不应该被接受。【参考方案10】:

Predicate.not( … )

java-11 提供了一个新方法Predicate#not

所以你可以否定方法引用:

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

【讨论】:

【参考方案11】:

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

import org.springframework.util.StringUtils;

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

这样做: return (str != null &amp;&amp; !str.isEmpty());

所以它会对isEmpty产生必要的否定效果

【讨论】:

【参考方案12】:

只要emptyStrings = s.filter(s-&gt;!s.isEmpty()).count();你就可以做到这一点

【讨论】:

以上是关于如何否定方法引用谓词的主要内容,如果未能解决你的问题,请参考以下文章

如何构建一个从关系中引用实体的谓词?

CKQuery 谓词有引用而不是字段?

iOS UIAutomation Scripting:引用键盘的正确谓词是啥?

Swift 中 Realm 查询的交叉引用谓词

为啥 std::find_if(first, last, p) 不采用引用谓词?

remove_if 中的否定谓词