如果我编写Predicate接口,我想在接口中编码它只是一个返回原始布尔值的函数,如下所示:
@FunctionalInterface
public interface Predicate<T> extends Function<T, Boolean> {
boolean test(T t);
@Override
default Boolean apply(T t) {
return Boolean.valueOf(test(t));
}
}
我想知道,Java 8 API设计人员选择将Predicate与Function完全分开是否有令人信服的理由?是否有一些证据表明他们认为这样做并决定反对呢?我想类似的问题适用于所有其他的特殊问题&#39;消费者(可以是功能&lt; T,Void&gt;),供应商(功能&lt; Void,T&gt;)和原始功能(如IntFunction(功能&lt;整数,T&gt;))等功能接口。
我对这一切的后果并没有深入而彻底地思考,所以我可能会遗漏一些东西。
编辑:一些答案强调了应用和测试之间的语义区别。我并不是说我不欣赏这种区别,我同意这种区别是有益的。我不明白为什么谓词不是一个函数的原因与例如List是Collection或Double是Number,它是一个Object。
如果Predicate(以及所有其他特殊的通用功能接口,例如Consumer,Supplier,IntUnaryOperator等)与Function有这种关系,它将允许人们在需要Function参数的位置使用它(想到的是什么)与其他函数组合,例如调用myFunction.compose(myPredicate)或避免在API中编写几个专用函数时,如上所述的自动(un)装箱实现就足够了)
编辑2:看看openjdk lambda项目,我发现用于将函数扩展到this commit from Brian Goetz on 2012-12-19的原始函数接口。在那段时间里,我无法找到任何lambda-dev或JSR专家组邮件列表发生变化的具体原因。
答案 0 :(得分:19)
Predicate<T>
中的方法会返回boolean
。 Function<T, Boolean>
中的方法返回Boolean
。他们不一样。虽然存在自动装箱,但是当原语可以使用时,Java方法不会使用包装类。此外,Boolean
之类的差异可以是null
,而boolean
可以是Consumer<T>
。
Consumer<T>
的情况更为不同。 void
中的方法具有返回类型return;
,这意味着它可以使用Function<T, Void>
隐式返回或返回,但return null;
中的方法必须使用{{1}}明确返回
答案 1 :(得分:12)
不需要这种可疑的继承层次结构。这些功能接口是可互换的。
Function<A,Boolean> f1=…;
Predicate<A> p1=…;
Predicate<A> p2=f1::apply;
Function<A,Boolean> f2=p1::test;
这适用于两个方向。那么为什么要有一个继承关系来宣传一个特定的方向呢?
答案 2 :(得分:6)
这不是你问题的直接答案,而是你将它用于什么?
请考虑以下情形:您希望将true / false映射到分别为false的值列表。
使用您的代码可以使用:
@FunctionalInterface
interface CustomPredicate<T> extends Function<T, Boolean> {
boolean test(T value);
@Override
default Boolean apply(T t) {
return test(t);
}
}
List<String> stringList = new ArrayList<>();
stringList.add("a");
stringList.add("hg");
stringList.add("dsl");
stringList.add("sldi");
stringList.add("ilsdo");
stringList.add("jlieio");
CustomPredicate<String> customPredicate = str -> (str.length() >= 3);
Map<Boolean, List<String>> mapping = stringList.stream()
.collect(Collectors.groupingBy(customPredicate));
然而,下面告诉我他们肯定会想到类似的东西,因为他们提供了分区方法:
List<String> stringList = new ArrayList<>();
stringList.add("a");
stringList.add("hg");
stringList.add("dsl");
stringList.add("sldi");
stringList.add("ilsdo");
stringList.add("jlieio");
Predicate<String> predicate = str -> (str.length() >= 3);
Map<Boolean, List<String>> mapping = stringList.stream()
.collect(Collectors.partitioningBy(predicate));
我能想到的一些原因是:
apply()
中只有Predicate
方法可用test()
方法,这是不直观的。CustomPredicate
包含两种类型的功能。这只会增加混乱。答案 3 :(得分:6)
在我看来,Function<T, R>
只是泛型函数的定义。如果所有FunctionalInterfaces
都实现Function
,则唯一的抽象方法必须命名为apply()
。在具体FunctionalInterface
之类的FilterFile
中,抽象方法boolean accept(File pathname)
是一个比Boolean apply(File)
更好的名称。
注释@FunctionalInterface
已将某个界面标记为可用作FunctionalInterface
。除了以通用方式处理它们之外,它们都实现基本接口是没有好处的。我不知道你什么时候不关心FunctionalInterface
的语义,以便让他们可以为他们打电话apply
。