为什么Java 8的ToIntFunction 不扩展Function

如果我写了ToIntFunction接口,我想在接口中编码它只是一个返回原始int的函数,如下所示:

@FunctionalInterface public interface ToIntFunction extends Function { int applyAsInt(T value); @Override default Integer apply(T value) { return Integer.valueOf(applyAsInt(value)); } } 

我想知道,有没有令人信服的理由让Java 8 API设计人员选择将原始备选方案与Function完全分开? 是否有一些证据表明他们认为这样做并决定反对呢? 我想类似的问题至少包括其他一些“特殊”function接口,如Consumer(可能是Function )和Supplier(Function )。

我没有深入和彻底地考虑过这个问题的所有后果,所以我可能会遗漏一些东西。

如果ToIntFunction(和其他原始通用function接口)与Function有这种关系,它将允许一个人在预期使用Function参数的位置使用它(想到的是与其他函数的组合,例如调用myFunction.compose(myIntFunction))或者当在如上所述的这种自动(非)装箱实现就足够时避免在API中编写几个专用函数。

这与这个问题非常相似: 为什么Java 8的Predicate 没有扩展Function 但我已经意识到答案可能因语义原因而不同。 因此,我正在重新设计这个问题的简单原始替代函数的问题,其中不存在任何语义,只是原始与包装类型,甚至消除了空包装对象的可能性。

JDK 8中的接口爆炸是Java中一个小问题的产物:缺乏值类型。

这意味着我们不能将原始类型与generics一起使用,因此,我们不得不使用包装类型。

换句话说,这是不可能的:

 Function myFunction; 

但这是:

 Function myFunction; 

这个问题是装箱/拆箱。 这可能变得昂贵并且使得处理原始数据类型的算法难以优化,因为不断需要为原始值创建包装器对象,反之亦然。

这解释了为什么JDK 8中存在大量接口,如FunctionIntFunction ,后者使用基本类型作为参数。

Lambda Mailing List中的某些内容对此进行了讨论,揭示了专家组正在努力解决这个问题。

lambda项目的规范负责人Brian Goetz在那里写道:

更一般地说:拥有专门的原始流(例如,IntStream)背后的哲学充满了令人讨厌的权衡 。 一方面,它有很多丑陋的代码重复界面污染等。另一方面,任何类型的盒装操作算法都很糟糕 ,并且没有减少过多的故事会很糟糕。 所以我们处在一个艰难的角落 ,我们正试图不让它变得更糟。

不让它变得更糟的技巧#1是:我们没有做所有八种原始类型。 我们做的是int,long和double; 所有其他人都可以通过这些来模拟。 可以说我们也可以摆脱int,但我们认为大多数Java开发人员都没有为此做好准备。 是的,会有对Character的调用,答案是“坚持使用int”。 (每个专业化预计为JRE足迹的约100K。)

技巧#2是:我们使用原始流来揭示在原始域中最好的事情(排序,减少)但不尝试复制你在盒装域中可以做的所有事情。 例如,正如Aleksey指出的那样,没有IntStream.into()。 (如果有的话,下一个问题是“哪里是IntCollection?IntArrayList?IntConcurrentSkipListMap?”意图是许多流可以作为参考流开始并最终作为原始流,但反之亦然。那没关系,那就是减少了所需的转换次数(例如,int – > T没有map的重载,int的函数没有专门化 – > T等)

可能在将来(也许是JDK 9),当我们获得Java中的值类型支持时,我们将能够摆脱(或至少不再需要使用)这些接口。

专家组遇到了几个设计问题,而不仅仅是这个问题。 保持向后兼容性的需要,要求或约束使事情变得困难,然后我们还有其他重要条件,例如缺少值类型,类型擦除和检查exception。 如果Java有第一个而缺少其他两个,那么JDK 8的设计将会非常不同。 所以,我们都必须明白这是一个很难权衡的问题,而且EG必须在某个地方划一条线并作出决定。

因为这意味着所有原始操作function都会产生自动盒子和拆箱操作的成本。

如果您不关心它的性能影响,只需使用Function<>的盒装版本。