Abstract: Lambdas in Java took a long time in coming, due to the considerable engineering effort put into incorporating them into the Java Language. Unfortunately checked exceptions are not managed as seamlessly as they should.
Welcome to the 221st issue of The Java(tm) Specialists' Newsletter, written at 30000 feet en route to London Stanstead, directly from Chania! I did not want to waste any time in taking advantage of my brand new Greek ID card. Puzzled looks by airport staff, followed by "Do you speak Greek?" Eager to impress, I answered in the affirmative, but in future I'll revert to my standard answer: "Oute mia lexi" - not even a single word :-)
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
It is a great pity that when Java Lambdas were designed and implemented, not enough care was put into making checked exceptions play along nicely. At first this did not bother me too much, but as I started using them more, I realized that this was a shortcoming that would get in the way time and time again.
Let's say we wanted to write a transformer that can convert one type of object into another. An example of that would be the DateFormat, which can either change a Date into a String with the format() method or a String into a Date with the parse() method. The most obvious functional interface I could think to use was the BiFunction, from the java.util.function package:
package java.util.function; @FunctionalInterface public interface BiFunction<T, U, R> { /** * Applies this function to the given arguments. * * @param t the first function argument * @param u the second function argument * @return the function result */ R apply(T t, U u); // etc. }
T would be the transformer class, for example the DateFormat. U would be the input
parameter, either Date or String. R would be the result. Thus if we wanted to format
a date, we could
simply use a method reference DateFormat::format
. But what if we want
to instead parse a String? The DateFormat.parse() method throws a checked exception.
However, the apply() method of BiFunction does not.
Java is the only main-stream language with checked exceptions that I know of. It reminds me of the joke my father used to tell, where this guy is driving along the highway and a traffic warning sounds on his radio: "Vorsicht, Vorsicht, ein Geisterfahrerer auf der A3!" (Beware of a person driving on the wrong side of highway A3 - but I prefer the German descriptive "ghost driver".) And the guy looks around him and says: "Just one? Hundreds!" Ahem, Sun engineers - perhaps there is a good reason why most languages only have unchecked exceptions?
The problem with the standard java.util.function.BiFunction
is that there
is no way to elegantly deal with checked exceptions. This is something that should
have
been included in the Java 8 lambda design. The architects did think of it, but did
not
anticipate how much of a pain it would be to deal with later. My workaround was to
declare a new type of BiFunction that has support for checked exceptions:
@FunctionalInterface public interface BiFunctionWithCE<T, U, R, X extends Exception> { R apply(T t, U u) throws X; }
To show how this can be used, I've defined a TransformerObjectPool. I am not in favour of generalized object pools, but they can be useful when it is more expensive to create the objects than to manage their reuse (e.g. threads, database connections, etc.). In this case, it is designed for objects that are not threadsafe and that should therefore only be used by a single thread at a time. This is one solution - another would be to store the objects inside thread locals. Our TransformerObjectPool will only ever create as many objects as we have threads concurrently trying to use the transformer objects. The object pool should thus grow to some size and stay there. We do not have a policy to retire objects that have not been used for a long time. I have kept it simple, just to illustrate the issue of dealing with exceptions.
public abstract class Transformer<T> { public final <R, U, X extends Exception> R transform( U u, BiFunctionWithCE<T, U, R, X> transformer) throws X { T t = takeTransformer(); try { return transformer.apply(t, u); } finally { putTransformer(t); } } protected abstract T takeTransformer(); protected abstract void putTransformer(T t); }
The transform() method is a bit difficult to understand. It takes three generic paramters. R is the return value of the transform method. For example if we format() a date, it would return a String. If we parsed a String date, it would return a Date object. U is the value that needs to be transformed. For example, if we are formatting a date, then U would be a Date object. Lastly, X is the exception that we might want to throw, for example a ParseException. The parameter that we pass in is our own special BiFunction that is able to throw checked exceptions from its functional method.
Here is our simple implementation with a single ConcurrentLinkedQueue to hold the pooled objects. If it ever finds the queue empty, that means that other threads are busy with the objects, and so we always create new objects with the Supplier:
import java.util.*; import java.util.concurrent.*; import java.util.function.*; /** * In this approach, we only have as many objects as we've ever * had concurrent threads trying to do parsing/formatting. This * way, we avoid creating ThreadLocals that might never get * cleaned up if the thread belongs to a thread pool. */ public class TransformerObjectPool<T> extends Transformer<T> { private final Queue<T> objects = new ConcurrentLinkedQueue<>(); private final Supplier<T> supplier; public TransformerObjectPool(Supplier<T> supplier) { this.supplier = supplier; } protected T takeTransformer() { T t = objects.poll(); if (t == null) { t = supplier.get(); } return t; } protected void putTransformer(T t) { objects.add(t); } }
The Supplier<T>
is used to create new objects
when the pool does not have sufficient. Supplier<T>
is a standard java.util.function.* class and is not
checked-exception-friendly. Thus if you need the get() method
to throw a checked exception, then you again need to define
a special Supplier, such as:
@FunctionalInterface public interface SupplierWithCE<T, X extends Exception> { T get() throws X; }
Let's have a look at how the TransformerObjectPool can be used. To make it a bit easier, we create a FormatterParser interface. In our case, we want to always format to a String or parse from a String.
public interface FormatterParser<T, X extends Throwable> { public String format(T t); public T parse(String s) throws X; }
Here is our solution for formatting and parsing dates using the standard ISO format and SimpleDateFormat:
import java.text.*; import java.util.*; public class FormattingUtilPooledLambdaQueue implements FormatterParser<Date, ParseException> { private final TransformerObjectPool<DateFormat> pool = new TransformerObjectPool<>( () -> new SimpleDateFormat("yyyy-MM-dd")); public String format(Date date) { return pool.transform(date, DateFormat::format); } public Date parse(String date) throws ParseException { return pool.transform(date, DateFormat::parse); } }
As you can see, lambdas make this code very elegant. Format
is reduced to simply return the transform method
parameterized with a method reference:
pool.transform(date, DateFormat::format)
.
Beautiful. And we even have support for the ParseException
thrown from the parse() method.
For completeness, here is a solution using ThreadLocal:
import java.text.*; import java.util.*; public class FormattingUtilThreadLocal implements FormatterParser<Date, ParseException> { public String format(Date date) { return getFormatter().format(date); } public Date parse(String date) throws ParseException { return getFormatter().parse(date); } private static final ThreadLocal<DateFormat> formatter = new ThreadLocal<DateFormat>() { protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } }; private static DateFormat getFormatter() { return formatter.get(); } }
I think most of my readers would be familiar with the trick of using ThreadLocal to create thread-confined objects. Furthermore, here is the code using the new Java Date API, which allows us to share an immutable DateTimeFormatter:
import java.time.*; import java.time.format.*; public class FormattingUtilJava8 implements FormatterParser<LocalDate, RuntimeException> { private static DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE; public String format(LocalDate date) { return formatter.format(date); } public LocalDate parse(String date) { return LocalDate.parse(date); } }
From my experiments, the ThreadLocal approach was generally the fastest, but had the disadvantage that managed threads kept the DateFormat objects alive longer than necessary. Second fastest was my TransformerObjectPool. And in third place, winning a bronze medal, is the DateTimeFormatter, which uses the new Java 8 Time and Date API. Thread safety through immutability comes at a price - more garbage. In this case, it created approximately 32% more junk than the TransformerObjectPool version. It depends of course on how you test it.
One of the issues with the TransformerObjectPool is that we have two bottlenecks that cause contention: the head and the tail of the queue. This problem was already discussed in our newsletter 214 on the CountingCompletionService. Instead of a single queue, we can mix things up a bit by creating an array of ConcurrentLinkedQueues and using the thread ID to find an offset. In order to avoid using the remainder function (%), we make the size of the pool a power of 2. We can then use simple bit masking to find our own queue. Here is how we could do it:
import java.util.*; import java.util.concurrent.*; import java.util.function.*; /** * To avoid a bottleneck at the head and tail of the * ConcurrentLinkedQueue, we create an array of queues. * Since the queues are created at startup based on the * desired concurrencyLevel, we do not need any additional * synchronization to protect the elements of the array. * The number of queues is always a power of 2, so that we * can use bit masking instead of remainder operations to * find our queue. */ public class TransformerObjectPoolArray<T> extends Transformer<T> { private static final int MAXIMUM_CAPACITY = 1 << 16; private final Queue<T>[] objects; private final Supplier<T> supplier; private final int mask; public TransformerObjectPoolArray(Supplier<T> supplier) { this(supplier, 16); } @SuppressWarnings("unchecked") public TransformerObjectPoolArray(Supplier<T> supplier, int concurrencyLevel) { this.supplier = supplier; objects = new Queue[sizeFor(concurrencyLevel)]; for (int i = 0; i < objects.length; i++) { objects[i] = new ConcurrentLinkedQueue<>(); } mask = objects.length - 1; } private static int sizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } protected T takeTransformer() { Queue<T> q = getMyQueue(); T t = q.poll(); if (t == null) { t = supplier.get(); } return t; } private Queue<T> getMyQueue() { long id = Thread.currentThread().getId(); return objects[((int) (id & mask))]; } protected void putTransformer(T t) { getMyQueue().add(t); } }
With our array based concurrent linked queue pool, we now have performance that is pretty close to using ThreadLocal. The design is a bit of a mess as it contains too much duplicate code. However, since this is really just an example of how lambdas manage checked exceptions, I will resist refactoring it further. We can leave that as an exercise for the Design Patterns Course.
Kind regards from London
Heinz
P.S. I appreciate that I have a lot of friends in London who would have liked to see me. It has been 5 years since I was effectively banished from the UK due to visas being required for South Africans. For this visit I will only see my sister. But I will be back soon for JAX London :-) It has been too long and I've started to forget just how bad real ale is ;-)
P.P.S. Jeffrey Falgout has written an alternative API to streams that allows us to throw checked exceptions.
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.