Java 8 Consumer and Supplier
Introduction
The Consumer and Supplier interfaces are a couple of Functional Interfaces that belong to the new Java 8 java.util.function package. As the package name states, these interfaces are meant to be used together with the new functional Java 8 features.
A complete list of the package interfaces and their descriptions may be found here: java.util.function (Java Platform SE 8).
Supplier
Suppliers represent a function that accepts no arguments and produce a result of some arbitrary type.
Suppliers may reference constructor methods:
Supplier<User> userSupplier = User::new; User user = userSupplier.get();
Suppliers may also reference static methods:
Supplier<User> userSupplier = UserFactory::produceUser; User user = userSupplier.get(); class UserFactory { public static User produceUser() { return new User(); } }
Instance methods are also available to be referenced by suppliers:
Supplier<User> userSupplier = this::produceUser; User user = userSupplier.get(); private User produceUser(){ return new User(); }
Consumer
Consumers represent a function that accepts a single argument of an arbitrary type and produce no result:
Consumer<User> userConsumer = (u) -> System.out.println("Username: " + u.getUsername()); userConsumer.accept(user);
Consumers may also be applied to streams of data in order to execute some given action against every stream element:
List<User> userList = ...; userList.stream().forEach((u) -> System.out.println("Username: " + u.getUsername()));
Streams will be covered in a later article.
Asynchronous processing
Even though Java 8 Consumers and Suppliers were included in order to supply functional features, they may remind us of the typical Publisher-Subscriber asynchronous processing architecture.
Java already provides a complete infrastructure in order to implement asynchronous processing, but one could also craft something like the following using Consumers and Suppliers (even if it is just for curiosity):
public class SupplierConsumer<T> extends Thread { private Supplier<T> supplier; private Consumer<T> consumer; private boolean shouldRun = true; public SupplierConsumer(Supplier<T> supplier, Consumer<T> consumer) { this.supplier = supplier; this.consumer = consumer; } @Override public void run() { while (shouldRun) { T item = supplier.get(); consumer.accept(item); } } }
BlockingQueue<User> usersQueue = new LinkedBlockingQueue<User>(); Supplier<User> userSupplier = new Supplier<User>() { @Override public User get() { try { return usersQueue.take(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }; Consumer<User> userConsumer = new Consumer<User>() { @Override public void accept(User user) { System.out.println("Processing user " + user.getUserId()); } }; new SupplierConsumer<>(userSupplier, userConsumer).start(); for (int i = 0; i < 100; i++) { usersQueue.put(new User(i, "user" + 1)); }
We use a LinkedBlockingQueue as the underlying data structure that holds the items to be processed since it is already synchronized internally.
The Supplier is responsible for taking items from the queue and the Consumer is responsible for process those items.
We left the SupplierConsumer class generic enough so it's just a matter of defining how the Supplier will fetch data that will be later passed to the consumer for processing.