为什么Java 8的ToIntFunction< T>扩展函数< T,整数>

为什么Java 8的ToIntFunction< T>扩展函数< T,整数>

问题描述:

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

If I wrote the ToIntFunction interface, i'd want to encode in the interface the fact that it's just a function that returns a primitive int, like this:

@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完全分开?是否有一些证据表明他们认为这样做并决定反对呢?我想类似的问题至少包括其他一些特殊功能接口,如Consumer(可能是Function< T,Void>)和Supplier(Function< Void,T>)。

I was wondering, is there a compelling reason Java 8 API designers chose to keep the primitive alternatives completely separate from Function? Is there some evidence that they considered doing so and decided against it? I guess similar question goes for at least some of the other 'special' functional interfaces like Consumer (could be Function<T, Void>) and Supplier (Function<Void, T>).

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

I haven't thought very deeply and thoroughly about all the ramifications of this, so I'm probably missing something.

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

If ToIntFunction (and the other primitive generic functional interfaces) had this relation with Function, it would allow one to use it in place where Function parameter is expected (what comes to mind is composition with other functions, e.g. calling myFunction.compose(myIntFunction) or to avoid writing several specialized functions in an API when such auto(un)boxing implementation as described above would be sufficient).

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

This is very similar to this question: Why doesn't Java 8's Predicate<T> extend Function<T, Boolean> but I've realized that the answer might be different for semantic reasons. Therefore i'm reformulating the question for this case of a simple primitive alternative to Function, where there can't be any semantics, just primitive vs. wrapped types and even possibility of the null wrapped object is eliminated.

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

The interface explosion in the JDK 8 is the product of one small problem in Java: the lack of value types.

这意味着我们不能将基本类型与泛型一起使用,因此,我们不得不使用包装类型。

This implies that we cannot use primitive types with generics, and therefore, we are forced to use wrapper types.

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

In other words, this is not possible:

Function<String, int> myFunction;

但这是:

Function<String, Integer> myFunction;

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

The problem with this is boxing/unboxing. This can become expensive and make algorithms dealing with primitive data types difficult to optimize due to the constant need of creating wrapper objects for the primitive values and vice versa.

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

This explains why there is an explosion of interfaces in the JDK 8, like Function and IntFunction, the latter using primitive types as arguments.

Lambda邮件列表显示专家组正在努力解决这个问题。

This was discussed at some point in the Lambda Mailing List revealing that the expert group was struggling with this.

Brian Goetz,lambda的规范领导者项目,在那里写道:

Brian Goetz, spec leader of the lambda project, wrote there:


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

More generally: the philosophy behind having specialized primitive streams (e.g., IntStream) is fraught with nasty tradeoffs. On the one hand, it's lots of ugly code duplication, interface pollution, etc. On the other hand, any kind of arithmetic on boxed ops sucks, and having no story for reducing over ints would be terrible. So we're in a tough corner, and we're trying to not make it worse.

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

Trick #1 for not making it worse is: we're not doing all eight primitive types. We're doing int, long, and double; all the others could be simulated by these. Arguably we could get rid of int too, but we don't think most Java developers are ready for that. Yes, there will be calls for Character, and the answer is "stick it in an int." (Each specialization is projected to ~100K to the JRE footprint.)

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

Trick #2 is: we're using primitive streams to expose things that are best done in the primitive domain (sorting, reduction) but not trying to duplicate everything you can do in the boxed domain. For example, there's no IntStream.into(), as Aleksey points out. (If there were, the next question(s) would be "Where is IntCollection? IntArrayList? IntConcurrentSkipListMap?) The intention is many streams may start as reference streams and end up as primitive streams, but not vice versa. That's OK, and that reduces the number of conversions needed (e.g., no overload of map for int -> T, no specialization of Function for int -> T, etc.)

当我们得到支持Java中的值类型,我们将能够摆脱(或至少不再需要使用)这些接口。

Probably, in the future (maybe JDK 9) when we get Support for Value Types in Java, we will be able to get rid of (or at least no longer need to use anymore) these interfaces.

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

The expert group struggled with several design issues, not just this. The need, requirement or constraint to keep backwards compatibility made things difficult, then we have other important conditions like the lack of value types, type erasure and checked exceptions. If Java had the first and lacked of the other two the design of JDK 8 would have been very different. So, we all must understand that it was a difficult problem with lots of tradeoffs and the EG had to draw a line somewhere and make a decision.