【问题标题】: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)

标签: java predicate java-8 negate


【解决方案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 内置功能。

【讨论】:

【解决方案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 有方法and、or 和negate。

但是,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( … )

        提供了一个新方法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();你就可以做到这一点

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2023-02-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多