The Suplier interface
The Supplier interface denotes a lambda expression
that generates an object when invoked. It is commonly used to implement lazy initialisation. This is
where we want to declare in advance how to create a particular object if and when it is required, rather than having to create
it up front. The Supplier implementation is the piece of code that determines how to initialise the object in question.
Lambda expressions that can be used as Suppliers
A lambda expression is a Supplier if:
- it does not take any parameters;
- it returns an object.
Note the first of the conditions above. In some cases, lazy initialisation may also be performed by a lambda expression that
is actually strictly speaking a Function because it needs an input value in order to determine what to initialise.
Syntax of a Supplier
Because a Supplier does not have any arguments, its syntax will have a characteristic () to denote the
empty argument list:
Supplier<List> listMaker = () -> new ArrayList();
Direct references to no-arg constructors can also be used as Suppliers. To do this, put the class name
followed by ::new as follows:
Supplier<List> listMaker = ArrayList::new;
Common uses of Supplier
As mentioned, a common use of a Supplier is for lazy initialisation. A concrete example of
this is the ThreadLocal.withInitial() method. In case you have not come across them, a thread local
variable has an independent value for each thread that references it. Whenever a given thread requests the value,
it retrieves the value that was last set by that specific thread; other threads can also retrieve their thread-specific
values at any time. But this leaves the question: what value is returned the first time that a given thread attempts to
retrieve the value?
We don't generally want the overhead of having to set an initial value in advance for every single thread running on the system,
without knowing that a particular thread is actually going to want to refer it. (Nor do we want the overhead of having to search
for all of the thread-local variables to initialise them every time a new thread is started.) And this is where
ThreadLocal.withInitial() comes to the rescue. We pass in a lambda expression that determines how to create
the initial value as and when it is required by a particular thread:
ThreadLocal<Calendar> cal =
ThreadLocal.withInitial(() -> Calendar.getInstance());
We could also write:
ThreadLocal<Calendar> cal =
ThreadLocal.withInitial(Calendar::getInstance);
At this point, no calendars have actually been constructed. The very first time a given thread calls cal.get(),
the Supplier will be invoked, and a new Calendar will be created for that thread. The next time the same
thread calls cal.get(), that same calendar object will then be returned to it. But no unnecessary objects are
created if a given thread never actually requires the use of a calendar.
Using Supplier with switchable configuration
Another common use of Supplier is with switchable configuration. In other words, we have particular
lines of code that we would like to execute if and only if a particular condition is true. For example, we might have
some lines of debug that we want to log out if debugging mode is enabled. We could write a central logging method
such as the following:
public static void debugLog(String msg) {
if (DEBUG) {
System.out.println(msg);
}
}
This is functionally sound, but has a minor disadvantage if the logging messages themselves are complex to generate.
Consider this example:
debugLog(String.format("%03d Command: %s", cmdNo, lookupCommand(code)));
Here, our code will go to the effort of looking up the
command and formatting the string even if debug logging is actually turned off!
The Supplier interface provides a solution to this. We can define our logging method as follows:
public static void debugLog(Supplier<String> msg) {
if (DEBUG) {
System.out.println(msg.get());
}
}
We would log information like this:
debugLog(() -> String.format("%03d Command: %s", cmdNo, lookupCommand(code)));
Now, thanks to lazy initialisation of the log message, the lookup and formatting operation are only performed
if they are actually required.
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.