为什么Java 8的ToIntFunction扩展函数 ?

时间:2022-11-02 21:58:27

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:

如果我写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));
    }
}

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>).

我想知道,为什么Java 8 API设计者选择将原始的替代方案与函数完全分离?是否有证据表明他们考虑这么做并决定反对?我猜类似的问题至少也适用于其他一些“特殊”的功能接口,如Consumer(可能是函数 )和供应商(函数 )。 ,> ,>

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

我还没有深入透彻地思考这一切的后果,所以我可能漏掉了什么。

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).

如果ToIntFunction(和其他原始通用功能接口)有这个关系函数,将允许一个预计在函数参数的地方使用它(是什么成分与其他功能,如调用myFunction.compose(myIntFunction)或避免写几个专门的函数在一个API等汽车(联合国)拳击实现上面描述就足够了)。

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.

这与这个问题非常相似:为什么Java 8的谓词 不扩展函数 ,但是我意识到,由于语义原因,答案可能不同。因此,我正在重新构造函数的简单原语替代的问题,在这种情况下,没有任何语义,只有原语和包装类型,甚至空包装对象的可能性都被排除了。 ,>

2 个解决方案

#1


12  

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

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

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;

But this is:

但这是:

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.

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

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

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

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

在Lambda邮件列表中的某个点上讨论了这个问题,这说明专家组正在努力解决这个问题。

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

lambda项目的规格负责人Brian Goetz写道:

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.

更一般地说:拥有专门的原始流(例如,IntStream)背后的哲学是充满了令人讨厌的权衡。一方面,它有很多丑陋的代码复制、界面污染等。另一方面,任何一种盒装操作系统的算法都很糟糕,没有减少ints的故事将是很糟糕的。所以我们处境艰难,我们正努力让情况变得更糟。

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.)

不要让事情变得更糟的第一个诀窍是:我们并不是在做所有的八种原始类型。我们用int, long和double;其他的都可以用这些来模拟。可以说我们也可以去掉int,但是我们认为大多数Java开发人员还没有准备好。是的,将会有对角色的要求,答案是“插入一个int”。(每个专门化被预测为JRE占用约100K。)

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.)

技巧#2是:我们使用原始流来公开最适合在原始域中完成的事情(排序、减少),但不要试图复制在盒装域中可以做的所有事情。例如,正如Aleksey所指出的,没有IntStream.into()。(如果有,下一个问题是“IntCollection在哪里?”IntArrayList吗?IntConcurrentSkipListMap ?)意图是许多流可能以引用流的形式开始,并以原始流的形式结束,但反之亦然。这样可以减少所需的转换数量(例如,int -> T没有重载映射,int -> T没有专门化函数等等)。

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.

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

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.

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

#2


2  

because this would imply that all primitive operational functions have the incurred cost of an automatic box and unbox operation.

因为这意味着所有的基本操作函数都要承担自动开箱和开箱操作的成本。

if you are not concerned about the performance implications of this, just use the boxed versions of Function<>.

如果您不关心这对性能的影响,只需使用函数<>的装箱版本。

#1


12  

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

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

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;

But this is:

但这是:

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.

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

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

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

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

在Lambda邮件列表中的某个点上讨论了这个问题,这说明专家组正在努力解决这个问题。

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

lambda项目的规格负责人Brian Goetz写道:

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.

更一般地说:拥有专门的原始流(例如,IntStream)背后的哲学是充满了令人讨厌的权衡。一方面,它有很多丑陋的代码复制、界面污染等。另一方面,任何一种盒装操作系统的算法都很糟糕,没有减少ints的故事将是很糟糕的。所以我们处境艰难,我们正努力让情况变得更糟。

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.)

不要让事情变得更糟的第一个诀窍是:我们并不是在做所有的八种原始类型。我们用int, long和double;其他的都可以用这些来模拟。可以说我们也可以去掉int,但是我们认为大多数Java开发人员还没有准备好。是的,将会有对角色的要求,答案是“插入一个int”。(每个专门化被预测为JRE占用约100K。)

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.)

技巧#2是:我们使用原始流来公开最适合在原始域中完成的事情(排序、减少),但不要试图复制在盒装域中可以做的所有事情。例如,正如Aleksey所指出的,没有IntStream.into()。(如果有,下一个问题是“IntCollection在哪里?”IntArrayList吗?IntConcurrentSkipListMap ?)意图是许多流可能以引用流的形式开始,并以原始流的形式结束,但反之亦然。这样可以减少所需的转换数量(例如,int -> T没有重载映射,int -> T没有专门化函数等等)。

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.

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

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.

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

#2


2  

because this would imply that all primitive operational functions have the incurred cost of an automatic box and unbox operation.

因为这意味着所有的基本操作函数都要承担自动开箱和开箱操作的成本。

if you are not concerned about the performance implications of this, just use the boxed versions of Function<>.

如果您不关心这对性能的影响,只需使用函数<>的装箱版本。