为啥 Java 8 的 Predicate<T> 不扩展 Function<T, Boolean>
Posted
技术标签:
【中文标题】为啥 Java 8 的 Predicate<T> 不扩展 Function<T, Boolean>【英文标题】:Why doesn't Java 8's Predicate<T> extend Function<T, Boolean>为什么 Java 8 的 Predicate<T> 不扩展 Function<T, Boolean> 【发布时间】:2014-05-06 03:27:51 【问题描述】:如果我编写了Predicate
接口,我想在接口中编码它只是一个返回原始boolean
的函数,如下所示:
@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
完全分开是否有令人信服的理由?是否有证据表明他们考虑这样做并决定反对?我想类似的问题适用于所有其他“特殊”功能接口,如Consumer
(可能是Function<T, Void>
)、Supplier
(Function<Void, T>
)和IntFunction
(Function<Integer, T>
)等原始函数。
我还没有深入彻底地考虑过这一切的后果,所以我可能遗漏了一些东西。
编辑:一些答案强调应用和测试之间的语义区别。我并不是说我不欣赏这种区别,我同意拥有这种区别是有益的。我不明白为什么Predicate
也不是Function in
,就像List
是Collection
或Double
是Number
,这是@ 987654339@.
如果Predicate
(以及所有其他特殊的通用功能接口,例如Consumer
、Supplier
、IntUnaryOperator
等)与Function
有这种关系,则可以在适当的位置使用它其中Function
参数是预期的(想到的是与其他函数的组合,例如调用myFunction.compose(myPredicate)
或避免在API 中编写几个专门的函数,当如上所述的自动(取消)装箱实现就足够了)
编辑 2:查看 openjdk lambda 项目,我发现原始功能接口用于扩展 Function
直到 this commit from Brian Goetz on 2012-12-19。我在当时的任何 lambda-dev 或 JSR 专家组邮件列表中都找不到具体的更改原因。
【问题讨论】:
@brian-goetz 是否愿意给出 EDIT 2 的规范答案? 【参考方案1】:Predicate<T>
中的方法返回boolean
。 Function<T, Boolean>
中的方法返回 Boolean
。她们不一样。尽管有自动装箱,但 Java 方法不使用包装类,而原语可以使用。另外,Boolean
可以是 null
而boolean
不能。
Consumer<T>
的情况就更不一样了。 Consumer<T>
中的方法返回类型为void
,也就是说它可以隐式返回或使用return;
返回,但Function<T, Void>
中的方法必须显式使用return null;
返回。
【讨论】:
这听起来并不令人信服。考虑到 Java 8 有默认方法,他们不能轻松地使Predicate<T>
扩展 Function<T>
并确保 Predicate 中有一个 default apply
方法委托给 test
吗?这样,消费者可以简单地实现test
,同时还可以获得一致的apply
语义【参考方案2】:
不需要这种可疑的继承层次结构。这些功能接口是可以互换的。
Function<A,Boolean> f1=…;
Predicate<A> p1=…;
Predicate<A> p2=f1::apply;
Function<A,Boolean> f2=p1::test;
这在两个方向都有效。那么为什么要有一个继承关系来宣传一个特定的方向呢?
【讨论】:
【参考方案3】:这不是您问题的直接答案,而是您将其用于什么目的?
考虑以下场景:您希望将 true/false 映射到分别为 true 和 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));
我能想到的一些原因是:
在您只期望test()
方法的 Predicate
中提供 apply()
方法并不直观。
功能接口旨在仅提供 一种 类型的基本功能(不包括链接或逻辑操作),在您的情况下,CustomPredicate
包含两种类型的功能。这只会增加混乱。
【讨论】:
另一个很好的理由是我们可以避免不必要的装箱/拆箱。 @EdwinDalorzo 我不建议用 apply() 替换 test() 方法 只有在没有替代的基于谓词的实现的设置中使用对象时才会进行自动(取消)装箱.接受泛型 Function 的函数总是可以有一个专门的 Predicate 重载以提高性能。 @skiwi 我的建议只会以另一种非原始、更通用的方式表达相同的操作。类/接口具有仅用于在更高抽象级别实现概念的方法并不少见(PrimitiveIterator.OfInt 扩展了 Iterator 并使用装箱;Deque 扩展了 Queue 并用相同但更恰当地命名为 offerLast 替换了 offer()) 【参考方案4】:在我看来Function<T, R>
只是泛型函数的定义。如果所有FunctionalInterfaces
都实现Function
,那么唯一的抽象方法必须命名为apply()
。在具体的FunctionalInterface
的上下文中,例如FilterFile
,抽象方法boolean accept(File pathname)
是一个比Boolean apply(File)
更好的名称。
注解@FunctionalInterface
已经将接口标记为可用作FunctionalInterface
。除了以通用方式处理它们之外,让它们都实现基本接口没有任何好处。我看不出你什么时候会不关心 FunctionalInterface
的语义,以便让它们都可以调用 apply
。
【讨论】:
我并不一定主张所有的功能接口都应该用Function代替。我建议预定义的泛型函数式接口可以扩展 Function,这将保留语义,同时允许在泛型中进行处理等其他事情(这似乎是一个相关的好处,尤其是在没有明显缺点的情况下)以上是关于为啥 Java 8 的 Predicate<T> 不扩展 Function<T, Boolean>的主要内容,如果未能解决你的问题,请参考以下文章
Java 8 - Predicate和Consumer接口函数式编程
Java—Java 8 新增特性详解(Predicate和Stream)