Introducing functional tuples in Java
Let's take a look at the following code:
Order order = ...
if (order != null) {
Customer customer = order.getCustomer();
if (customer != null) {
Address address = customer.getAddress();
if (address != null) {
String customerNo = customer.getNumber();
String city = address.getCity();
if (customerNo != null && city != null) {
// Do something (like validation)
}
}
}
}
Ok, to be honest - this code doesn't match the way we write code in 2021. So what are the opportunities to come up with?
We could use a bean validation framework like Hibernate. In most cases it would do the job by annotating fields with some validation annotations, and we are done. In some other cases, such as cross field validation, we can probably write our own validator and are done. But sometimes, in more complex cases, like cross object validation, Hibernate won't do the job, or is awful to use.
In these cases, we end up writing code like in the snippet above, which is incomprehensible and hard to follow.
There is functional-tuples to help us out:
Tuple.of(order) // Tuple1<Order>
.map1(Order::getCustomer) // Tuple1<Customer>
.unfold(Customer::getAddress) // Tuple2<Customer, Address>
.map1(Customer::getNumber) // Tuple2<String, Address>
.map2(Address::getCity) // Tuple2<String, String>
.ifPresent((customerNo, city) -> {
// Do something (like validation)
});
So what's going on here? Let's go through each line step by step:
- Wrap the
Order
-object intoTuple1
, which is a tuple with one component. - Map the first component (which is the only one in
Tuple1
) toCustomer
usingmap1
. - Turn the
Tuple1
intoTuple2
(two elements) by usingunfold
, which maps the rightmost component ofTuple1
over a given function, and appends its result. - We use
map1
over theTuple2
to map theCustomer
to thenumber
-Field in the first component. - Apply
map2
over theTuple
to map theAddress
to thecity
-Field in the second component. - Use
ifPresent
to check if each component is not null and apply a function.
What's the deal with it? The big advantages are:
- It's null-safe, because the higher-order functions (
mapX
,unfold
,ifX
, etc.), do all the null-checks needed, just as you know fromOptional
. - It's expressive, comprehensible and modern.
Tuple1<T1>
to Tuple6<T1, T2, T3, T4, T5, T6>
functional-tuples defines its own functional interfaces as extension to Function
and Consumer
from java.util.function
- mainly to support multiple parameters
and exceptions.
You are able to write code like this, and throw checked exceptions:
Order order = ...
try {
Tuple.of(order.getId(), order.getInvoicePath())
.ifPresent((orderId, path) -> {
new FileInputStream(path);
});
} catch (FileNotFoundException e) {
// ...
}