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 extends Function {
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) and Supplier (Function).
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).
This is very similar to this question: Why doesn't Java 8's Predicate extend Function 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.
解决方案
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 myFunction;
But this is:
Function 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.