为什么Java 8的Predicate <t>不扩展Function <t,boolean =“”> </t,> </t>

时间:2014-03-27 09:10:24

标签: java lambda functional-programming predicate java-8

如果我编写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专家组邮件列表发生变化的具体原因。

4 个答案:

答案 0 :(得分:19)

Predicate<T>中的方法会返回booleanFunction<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