Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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));
    }
}

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.

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

like image 753
mkadunc Avatar asked Mar 27 '14 14:03

mkadunc


People also ask

Can we extend functional interface in Java?

A functional interface can extends another interface only when it does not have any abstract method.

What are different functional interfaces in Java 8?

From Java 8 onwards, lambda expressions can be used to represent the instance of a functional interface. A functional interface can have any number of default methods. Runnable, ActionListener, Comparable are some of the examples of functional interfaces.

What is@ FunctionalInterface?

Annotation Type FunctionalInterface If an interface declares an abstract method overriding one of the public methods of java. lang. Object , that also does not count toward the interface's abstract method count since any implementation of the interface will have an implementation from java.

What is the purpose of functional interface in Java?

A functional interface is a special kind of interface with exactly one abstract method in which lambda expression parameters and return types are matched. It provides target types for lambda expressions and method references.


2 Answers

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

like image 165
Edwin Dalorzo Avatar answered Nov 05 '22 01:11

Edwin Dalorzo


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

like image 42
aepurniet Avatar answered Nov 05 '22 01:11

aepurniet