为什么Java 8的ToIntFunction <T>不扩展Function <T,Integer>

mka*_*unc 6 java java-api java-8 functional-interface

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

@FunctionalInterface
public interface ToIntFunction<T> extends Function<T, Integer> {
    int applyAsInt(T value);

    @Override
    default Integer apply(T value) {
        return Integer.valueOf(applyAsInt(value));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

Edw*_*rzo 14

JDK 8中的界面爆炸是Java 中一个小问题的产物:缺乏值类型.

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

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

Function<String, int> myFunction;
Run Code Online (Sandbox Code Playgroud)

但这是:

Function<String, Integer> myFunction;
Run Code Online (Sandbox Code Playgroud)

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

这就解释了为什么有接口在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中的值类型支持时,我们将能够摆脱(或至少不再需要使用)这些接口.

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


aep*_*iet 2

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

如果您不关心这对性能的影响,只需使用Function<>.