Java › Functional Interfaces & Method References
How do the primitive-specialized functional interfaces (IntFunction, ToIntFunction, IntPredicate, IntUnaryOperator, etc.) avoid the performance cost of autoboxing, and when should you prefer them over the generic equivalents?
IntermediateAnswer
The generic Function<Integer, Integer> and similar boxed-type functional interfaces incur autoboxing/unboxing overhead on every invocation (wrapping a primitive int into an Integer object and back), which can be measurably significant in a performance-sensitive hot path processing large volumes of primitive data -- the primitive-specialized interfaces (IntFunction<R> takes a primitive int without boxing, ToIntFunction<T> returns a primitive int without boxing, IntUnaryOperator both takes and returns primitive int, etc.) avoid this boxing overhead entirely, making them the recommended choice specifically for performance-sensitive code operating on primitive numeric streams (like IntStream), while the generic versions remain perfectly fine for typical, non-performance-critical code.
// Generic version: boxing overhead on every call
Function<Integer, Integer> square1 = x -> x * x;
// Primitive-specialized: no boxing overhead
IntUnaryOperator square2 = x -> x * x;
IntStream.rangeClosed(1, 1_000_000)
.map(square2) // IntStream.map() specifically expects IntUnaryOperator, avoiding boxing entirely
.sum();Real-world example A numerical computation processing a stream of a million primitive integers uses IntStream combined with IntUnaryOperator throughout, avoiding the autoboxing overhead that using the generic Function<Integer,Integer> equivalent across a million elements would have measurably added to the computation's total runtime.