如果我编写了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));
}
}
我想知道,有没有令人信服的理由让Java 8 API设计人员选择将原始备选方案与Function完全分开?是否有一些证据表明他们认为这样做并决定反对呢?我想类似的问题至少可以解决其他一些特殊的&#39;消费者(可以是功能&lt; T,Void&gt;)和供应商(功能&lt; Void,T&gt;)等功能接口。
我对这一切的后果并没有深入而彻底地思考,所以我可能会遗漏一些东西。
如果ToIntFunction(和其他原始的通用功能接口)与Function有这种关系,它将允许人们在预期使用Function参数的地方使用它(想到的是与其他函数的组合,例如调用myFunction.compose) (myIntFunction)或避免在API中编写几个专用函数时,如上所述的自动(非)装箱实现就足够了。
这与这个问题非常相似:Why doesn't Java 8's Predicate<T> extend Function<T, Boolean>但我已经意识到答案可能因语义原因而有所不同。因此,我为这个简单原始替代函数的情况重新制定了问题,其中不存在任何语义,只是原始与包装类型,甚至可以消除空包装对象的可能性。
答案 0 :(得分:14)
JDK 8中的界面爆炸是Java中一个小问题的产物:缺少值类型。
这意味着我们不能将原始类型与泛型一起使用,因此,我们不得不使用包装类型。
换句话说,这是不可能的:
Function<String, int> myFunction;
但这是:
Function<String, Integer> myFunction;
这个问题是装箱/拆箱。这可能变得昂贵,并且由于不断需要为原始值创建包装器对象而使处理原始数据类型的算法难以优化,反之亦然。
这解释了为什么JDK 8中的接口爆炸,如Function
和IntFunction
,后者使用基本类型作为参数。
在Lambda Mailing List的某些方面对此进行了讨论,揭示了专家组正在努力解决这个问题。
lambda项目的规范负责人Brian Goetz在那里写道:更一般地说:拥有专门原语的哲学 流(例如,IntStream)充满了讨厌的权衡。在一个 手,它有很多难看的代码重复,界面污染等等。 另一方面,盒装操作上的任何算术都很糟糕,和 没有减少过量的故事会很糟糕。所以我们是 在一个艰难的角落,我们试图不让它变得更糟。
不要让情况变得更糟的诀窍是:我们没有做到全部八 原始类型。我们做int,long和double;所有其他人 可以通过这些来模拟。可以说我们也可以摆脱int, 但我们认为大多数Java开发人员都没有为此做好准备。是, 将会有人物的召唤,答案是&#34;坚持下去 INT&#34。 (每个专业化预计为JRE约100K 足迹。)
技巧#2是:我们使用原始流来暴露事物 最好在原始域(排序,减少)完成但不尝试 复制您在盒装域中可以执行的所有操作。例如, 正如阿列克谢指出的那样,没有IntStream.into()。 (如果有的话, 接下来的问题是&#34; IntCollection在哪里? IntArrayList? IntConcurrentSkipListMap?)意图是许多流可能以 引用流并最终作为原始流,但反之亦然。 没问题,这减少了所需的转换次数(例如,没有 int的映射重载 - &gt; T,没有专门的函数用于int - &gt; T等。)
可能在未来(可能是JDK 9),当我们在Java中获得Support for Value Types时,我们将能够摆脱(或至少不再需要使用)这些接口。
专家组在解决几个设计问题时遇到了困难,而不仅仅是这个问题。保持向后兼容性的需要,要求或约束使事情变得困难,然后我们还有其他重要条件,例如缺少值类型,类型擦除和检查异常。如果Java有第一个而缺少其他两个,那么JDK 8的设计将会非常不同。所以,我们都必须明白这是一个很难权衡的问题,而且EG必须在某个地方划一条线并作出决定。
答案 1 :(得分:2)
因为这意味着所有原始操作功能都会产生自动盒子和拆箱操作的费用。
如果您不关心此问题的性能影响,请使用Function<>
的盒装版本。