Why doesn't Java 8's ToIntFunction<T> extend Function<T, Integer>

时间秒杀一切 提交于 2019-12-01 17:28:59

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;

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.

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

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

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

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

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.

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.

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!