Java 8 Primitive Functional Interfaces Tutorial
Introduction
This tutorial explains Java 8's primitive functional interfaces -IntPredicate, ToIntFunction, IntUnaryOperator, ObjIntConsumer and the rest. Most of the 43 interfaces in java.util.function are of this kind, and they are why IntStream, LongStream and DoubleStream exist. We will start with the problem they solve - boxing - and the naming scheme that makes all of them easy to read. We will then run an example using each family, and finish with practical advice on when to use them.(This tutorial assumes you know the generic interfaces covered in the java.util.function package tutorialClick to read: java.util.function package tutorial.)
The problem: generics and boxing
Java generics only work with reference types, soFunction<int, int> is not allowed; you have to write Function<Integer, Integer>. Every call then boxes its int argument into an Integer objectClick to read: Integer object, unboxes it to do the arithmetic, and boxes the result again. For values outside the small cached range (-128 to 127), each box is a new object. In a stream of a million numbers, that is millions of short-lived objects and a lot of extra work for the garbage collector.
int, long or double directly. Their abstract methods have different names - applyAsInt(), test(int), getAsDouble() - so that a primitive value never has to pass through an Object parameter.
How to read their names
There are a lot of them, but the names follow a few simple rules. Once you know the rules, you can tell what any of them does from its name.
IntPredicate,IntConsumer,IntFunction<R>: the PredicateClick to read: Predicate, ConsumerClick to read: Consumer and FunctionClick to read: Function you know, with anintargument.IntSupplier,BooleanSupplier: a SupplierClick to read: Supplier of anintor aboolean.ToIntFunction<T>,ToDoubleBiFunction<T, U>: take objects, return a primitive.IntToDoubleFunction,LongToIntFunction: convert one primitive type into another.IntUnaryOperator,DoubleBinaryOperator: the primitive operatorsClick to read tutorial: Java 8 UnaryOperator and BinaryOperator Tutorial.ObjIntConsumer<T>: a BiConsumerClick to read tutorial: Java 8 BiFunction, BiConsumer and BiPredicate Tutorial of an object and anint.
Java 8 code example of primitive functional interfaces
The example uses a smallEmployee class with a name and a salary.
Employee.java
//Employee.java
public class Employee {
private final String name;
private final double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public double getSalary() { return salary; }
@Override
public String toString() { return name + " (" + salary + ")"; }
}
Java 8 code showing IntPredicate, IntUnaryOperator, ToIntFunction, IntFunction, IntSupplier and ObjIntConsumer
//PrimitiveInterfaces.java
import java.util.Arrays;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleBinaryOperator;
import java.util.function.Function;
import java.util.function.IntBinaryOperator;
import java.util.function.IntConsumer;
import java.util.function.IntFunction;
import java.util.function.IntPredicate;
import java.util.function.IntSupplier;
import java.util.function.IntToDoubleFunction;
import java.util.function.IntUnaryOperator;
import java.util.function.ObjIntConsumer;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.stream.IntStream;
public class PrimitiveInterfaces {
public static void main(String[] args) {
// 1. The generic interfaces box: each int becomes an Integer object
Function<Integer, Integer> plusOneBoxed = n -> n + 1;
Integer a = plusOneBoxed.apply(999);
Integer b = plusOneBoxed.apply(999);
System.out.println("1. boxed results equal? " + a.equals(b)
+ ", same object? " + (a == b));
// 2. The primitive versions take and return plain ints
IntUnaryOperator plusOne = n -> n + 1;
IntPredicate isEven = n -> n % 2 == 0;
IntBinaryOperator larger = Math::max;
System.out.println("2. plusOne(999) = " + plusOne.applyAsInt(999)
+ ", isEven(42) = " + isEven.test(42)
+ ", larger(7, 12) = " + larger.applyAsInt(7, 12));
// 3. IntStream methods are declared with primitive interfaces
int sumOfEvenSquares = IntStream.rangeClosed(1, 10)
.filter(isEven) // IntPredicate
.map(n -> n * n) // IntUnaryOperator
.sum();
System.out.println("3. sum of even squares 1..10 = " + sumOfEvenSquares);
// 4. To and from objects: ToIntFunction, ToDoubleFunction and IntFunction
List<Employee> staff = Arrays.asList(new Employee("Tom Jones", 5200),
new Employee("Harry Major", 4100), new Employee("Nancy Smith", 5800));
ToIntFunction<Employee> nameLength = e -> e.getName().length();
ToDoubleFunction<Employee> salary = Employee::getSalary;
IntFunction<String> stars = n -> new String(new char[n]).replace('\0', '*');
System.out.printf("4. total name length = %d, average salary = %.2f, stars(5) = %s%n",
staff.stream().mapToInt(nameLength).sum(),
staff.stream().mapToDouble(salary).average().getAsDouble(),
stars.apply(5));
// 5. Between primitive types: IntToDoubleFunction, DoubleBinaryOperator
IntToDoubleFunction daysToWeeks = days -> days / 5.0;
DoubleBinaryOperator weightedAverage = (x, y) -> x * 0.7 + y * 0.3;
System.out.println("5. 12 days = " + daysToWeeks.applyAsDouble(12) + " weeks, weighted = "
+ weightedAverage.applyAsDouble(80, 60));
// 6. Suppliers and consumers of primitives
int[] counter = {0};
IntSupplier nextTicket = () -> ++counter[0];
IntConsumer printTicket = n -> System.out.print("#" + n + " ");
BooleanSupplier ticketsLeft = () -> counter[0] < 3;
System.out.print("6. ");
while (ticketsLeft.getAsBoolean()) {
printTicket.accept(nextTicket.getAsInt());
}
System.out.println();
// 7. ObjIntConsumer: an object and an int, without boxing the int
ObjIntConsumer<String> repeat = (text, times) -> {
for (int i = 0; i < times; i++) System.out.print(text);
};
System.out.print("7. ");
repeat.accept("ab", 3);
System.out.println();
}
}
1. boxed results equal? true, same object? false 2. plusOne(999) = 1000, isEven(42) = true, larger(7, 12) = 12 3. sum of even squares 1..10 = 220 4. total name length = 31, average salary = 5033.33, stars(5) = ***** 5. 12 days = 2.4 weeks, weighted = 74.0 6. #1 #2 #3 7. ababab
Explanation of the code
- Part 1: two calls of the boxed
Functionwith the same input give equalIntegers that are different objects - proof that each call allocated a new box. (It is also why==must never be used to compareIntegers.) - Part 2:
IntUnaryOperator,IntPredicateandIntBinaryOperatorwork on plainints. Note the method names:applyAsInt()andtest().Math::maxmatchesIntBinaryOperatorbecauseMath.max(int, int)takes and returns ints. - Part 3:
IntStream's methods are declared with primitive interfaces:filter()takes anIntPredicateandmap()anIntUnaryOperator, so the whole pipeline runs without a singleIntegerobject. See IntStream.range()Click to read: IntStream.range(). - Part 4:
mapToInt()takes aToIntFunctionandmapToDouble()aToDoubleFunction; both turn a stream of objects into a primitive stream, which hassum()andaverage().IntFunction<String>goes the other way, from anintto an object. - Part 5:
IntToDoubleFunctionconverts between primitive types, andDoubleBinaryOperatorcombines two doubles. - Part 6:
IntSupplier,IntConsumerandBooleanSupplierproduce and consume primitives. The counter is a one-element array because a lambda can only use effectively finalClick to read tutorial: Java 8 Lambdas: Effectively Final, Capture and this local variables; the array reference stays the same while its content changes. - Part 7:
ObjIntConsumertakes an object and anint- the shape of an accumulator that adds a number to a container.
When to use them
- Number crunching in streams: use
mapToInt(),mapToLong()ormapToDouble()as early as possible in a numeric pipeline, thensum(),average()orsummaryStatistics(). The summarizing collectorsClick to read: summarizing collectors useToIntFunctionand its relatives for the same reason. - Your own APIs: if a method takes a function that works on numbers and may be called millions of times, declare the parameter as
IntUnaryOperatorrather thanFunction<Integer, Integer>. - Back to objects:
boxed()turns anIntStreaminto aStream<Integer>, andmapToObj()takes anIntFunction- use them when you need to collect into aList. - Do not over-optimise: for a few hundred values, the generic interfaces are perfectly fine. Choose the primitive ones where the volume is high or where the API already uses them.
- Missing types: there are no versions for
byte,short,charorfloat. Use theintordoubleversions;String.chars(), for example, returns anIntStream.
Summary
In this tutorial we first saw why primitive functional interfaces exist: generic interfaces such asFunction<Integer, Integer> box every value, while IntUnaryOperator and its relatives work on plain int, long and double values. Next we learned the naming rules - IntXxx, ToIntXxx, IntToLongXxx, the operators and ObjIntConsumer. We then ran an example using each family, including how IntStream and mapToInt() rely on them. Lastly, we listed when to use them and when the generic versions are fine.Java 8 Tutorials
Java 8 UnaryOperator and BinaryOperator TutorialClick to read tutorial: Java 8 UnaryOperator and BinaryOperator Tutorial Java 8 Lambdas: Effectively Final, Capture and thisClick to read tutorial: Java 8 Lambdas: Effectively Final, Capture and this Java 8 CompletableFuture Tutorial with ExamplesClick to read tutorial: Java 8 CompletableFuture Tutorial with Examples Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Collectors.toMap Tutorial with ExamplesClick to read tutorial: Java 8 Collectors.toMap Tutorial with Examples Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial
Java 8 UnaryOperator and BinaryOperator TutorialClick to read tutorial: Java 8 UnaryOperator and BinaryOperator Tutorial Java 8 Lambdas: Effectively Final, Capture and thisClick to read tutorial: Java 8 Lambdas: Effectively Final, Capture and this Java 8 CompletableFuture Tutorial with ExamplesClick to read tutorial: Java 8 CompletableFuture Tutorial with Examples Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Collectors.toMap Tutorial with ExamplesClick to read tutorial: Java 8 Collectors.toMap Tutorial with Examples Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial