In this post let’s look at function composition using two compose functions provided in the Function interface - compose and andThen.
Function composition results in a reuseable function which itself is a combination of other functions.
Basically, there are two ways to achieve Function composition. They are compose and andThen.
Let’s create two functions. One which sums up and other squares up itself.
Function<Integer, Integer> sum = x -> x + x; Function<Integer, Integer> square = y -> y * y;
Next, let’s combine them, using compose and andThen.
Integer sumAndSquareResult = sum.compose(square).apply(3); // Returns 18 Integer squareAndSumResult = sum.andThen(square).apply(3); // Returns 36 System.out.println("sum.compose(square): " + sumAndSquareResult); System.out.println("sum.andThen(square): " + squareAndSumResult);
The difference between compose and andThen is the order in which they execute the functions.
- compose executes the parameter first (square) followed by the caller (sum)
- andThen executes the caller first (sum) followed by the parameter (square)
Let’s take it further and see how we can apply composition on
BiFunction functional interfaces.
Let’s use cars as our data set for examples below.
Let’s start by introducing a basic function - byManufacturer that filters cars based on Manufacturer.
BiFunction<String, List<Car>, List<Car>> byManufacturer = (manufacturer, cars) -> cars.stream() .filter(car -> car.getManufacturer().equals(manufacturer)) .collect(Collectors.toList());
byManufacturer is a BiFunction. They take two arguments.
Let’s also create few basic functions that sorts a list of cars from costliest to cheapest(affordable I must say!) price and a function that returns the top most car in a list.
Comparator<Car> carPriceComparator = (car1, car2) -> Double.compare(car2.getPrice(),car1.getPrice()); Function<List<Car>, List<Car>> sortByPrice = cars -> cars.stream() .sorted(carPriceComparator) .collect(Collectors.toList()); Function<List<Car>, Optional<Car>> first = cars -> cars.stream().findFirst();
Now that we have few functions built, let’s see how we can use them to compose new functions.
The Most Expensive Car
We can find the most expensive car in our inventroy by sorting the cars by price and then taking the top one from the sorted list.
Function<List<Car>, Optional<Car>> costliest = first.compose(sortByPrice); Optional<Car> costliestCar = costliest.apply(cars); System.out.println(costliestCar.isPresent() ? costliestCar.get() : null);
Car [manufacturer=Mercedes, model=Benz, year=2019, price=40000.0]
The Most Expensive Car By Toyota
Let’s see how to get the most expensive car by Toyota in our cars inventroy.
In our previous example, we wrote a function to get us the most expensive car. We can use this function in composition with a filter functions which gives all the cars based on car type to achieve this..
BiFunction<String, List<Car>, Optional<Car>> highestByCarType = byManufacturer.andThen(costliest); Optional<Car> costliestToyotaCar = highestByCarType.apply("Toyota", cars); System.out.println(costliestToyotaCar.isPresent() ? costliestToyotaCar.get() : null);
Car [manufacturer=Toyota, model=Camry, year=2018, price=24000.0]
Above are few examples of functional interfaces and functional compositions. The use cases around composition of functions are endless and can be customised for our needs.
Your feedback is always appreciated. Happy coding!