Consumer interface in Java

The Consumer interface was introduced in Java 1.8. Below is a an excerpt from the official documentation

Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects.

I want to talk about the Consumer functional interface in this article because I find it very useful and use it often.

Consumer definition

Consumer is an interface with as single abstract method accept(T t).

interface Consumer<T> {
    public void accept(T t);
}

The accept method, as its name suggests, "consumes" a value and performs a specific operation on it.

Uses of Consumer interface

  1. The behavior (accept method) of the consumer interface is well known. As a result, it increases the readability of the code.
  2. Enhances code reusability by leveraging pre-existing interfaces rather than creating custom ones for the same behavior.
  3. As it is a functional interface, it can be efficiently defined using lambda expressions.
  4. The Consumer interface is commonly utilized in many new Java methods, such as those found in the Stream class.
  5. The Consumer instances can be executed sequentially by utilizing the andThen method.

Example of Consumer interface

Let us view some examples of Consumer interface. To gauge its usability, we will first try to solve the problem without Consumer interface.

Problem Statement Let us consider a class PerformLongOperation which is doing some complex operation and keeps posting progress of its update to the listener. The listener needs to be passed as a parameter of doTask function which perform the actual task.

Solution 1: Using interface Here, we will use IOnTaskUpdateCallback interface to define the callback contract. Below will be one solution

class HelloWorld {
    static class PerformLongOperation {
        public void doTask(IOnTaskUpdateCallback onTaskUpdatedCallback) {
            onTaskUpdatedCallback.onProgressUpdated(0);
            for (int i = 10; i <= 100; i+=10) {
                try {
                    // add sleep to show task is being done
                    Thread.sleep(1000);
                } catch (InterruptedException exp) {
                    // ignoring for now
                }
                onTaskUpdatedCallback.onProgressUpdated(i);
            }
        }
    }

    interface IOnTaskUpdateCallback {
        // called when any update in the progress
        void onProgressUpdated(int progress);
    }

    public static void main(String[] args) {
        PerformLongOperation longGoing = new PerformLongOperation();
        longGoing.doTask(new IOnTaskUpdateCallback() {
            @Override
            public void onProgressUpdated(int progress) {
                System.out.println("Progress is: " + progress);
            }
        });
    }
}

Solution 1.1: Using interface and lambda We can make our main() method more concise by using lambda like below

public static void main(String[] args) {
    PerformLongOperation longGoing = new PerformLongOperation();
    longGoing.doTask(progress -> System.out.println("Progress is: " + progress));
}

Solution 2: Using Consumer Since, IOnTaskUpdateCallback satisfies all condition for being a Consumer, we can just use Consumer rather than defining a new one new interface.

class HelloWorld {
    static class PerformLongOperation {
        public void doTask(Consumer<Integer> onTaskUpdatedCallback) {
            onTaskUpdatedCallback.accept(0);
            for (int i = 10; i <= 100; i+=10) {
                try {
                    // add sleep to show task is being done
                    Thread.sleep(1000);
                } catch (InterruptedException exp) {
                    // ignoring for now
                }
                onTaskUpdatedCallback.accept(i);
            }
        }
    }

    public static void main(String[] args) {
        PerformLongOperation longGoing = new PerformLongOperation();
        longGoing.doTask(progress -> System.out.println("Progress is: " + progress));
    }
}

To sum up, the Consumer functional interface in Java simplifies coding, enhances reusability, and streamlines development, making it a valuable tool for efficient programming.