如何否定方法引用谓词
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<String>
- 这非常难看。
@assylias 用作Predicate<String> p = (Predicate<String>) String::isEmpty;
和p.negate()
。
@SotiriosDelimanolis 我知道,但这违背了目的 - 在这种情况下,我宁愿写 s -> !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 -> !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<String>
),那作品。我认为 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 更喜欢 PredicatePredicate
有方法and
、or
和negate
。
但是,String::isEmpty
不是 Predicate
,它只是一个 String -> Boolean
lambda,它仍然可以变成任何东西,例如Function<String, Boolean>
。 类型推断是首先需要发生的事情。 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 && !str.isEmpty());
所以它会对isEmpty
产生必要的否定效果
【讨论】:
【参考方案12】:只要emptyStrings = s.filter(s->!s.isEmpty()).count();
你就可以做到这一点
【讨论】:
以上是关于如何否定方法引用谓词的主要内容,如果未能解决你的问题,请参考以下文章
iOS UIAutomation Scripting:引用键盘的正确谓词是啥?