Java 8 Supplier & Consumer explanation for the layperson

The reason you're having difficulty grasping the meaning of functional interfaces such as those in java.util.function is that the interfaces defined here do not have any meaning! They are present primarily to represent structure, not semantics.

This is atypical for most Java APIs. The typical Java API, such as a class or interface, has meaning, and you can develop a mental model for what it represents and use that to understand the operations on it. Consider java.util.List for example. A List is a container of other objects. They have a sequence and an index. The number of objects contained in the list is returned by size(). Each object has an index in the range 0..size-1 (inclusive). The object at index i can be retrieved by calling list.get(i). And so forth.

The functional interfaces in java.util.function don't have any such meaning. Instead, they're interfaces that merely represent the structure of a function, such as the number of arguments, the number of return values, and (sometimes) whether an argument or return value is a primitive. Thus we have something like Function<T,R> which represents a function that takes a single argument of type T and returns a value of type R. That's it. What does that function do? Well, it can do anything ... as long as it takes a single argument and returns a single value. That's why the specification for Function<T,R> is little more than "Represents a function that accepts one argument and produces a result."

Clearly, when we're writing code, it has meaning, and that meaning has to come from somewhere. In the case of the functional interfaces, the meaning comes from the context in which they're used. The interface Function<T,R> has no meaning in isolation. However, in the java.util.Map<K,V> API, there is the following:

V computeIfAbsent(K key, Function<K,V> mappingFunction)

(wildcards elided for brevity)

Ah, this use of Function is as a "mapping function". What does that do? In this context, if key is not already present in the map, the mapping function is called and is handed the key and is expected to produce a value, and the resulting key-value pair is inserted into the map.

So you can't look at the specification for Function (or any of the other functional interfaces, for that matter) and attempt to discern what they mean. You have to look at where they're used in other APIs to understand what they mean, and that meaning applies only to that context.


This is Supplier:

public Integer getInteger() {
    return new Random().nextInt();
}

This is Consumer:

public void sum(Integer a, Integer b) {
    System.out.println(a + b);
}

So in layman terms, a supplier is a method that returns some value (as in it's return value). Whereas, a consumer is a method that consumes some value (as in method argument), and does some operations on them.

Those will transform to something like these:

// new operator itself is a supplier, of the reference to the newly created object
Supplier<List<String>> listSupplier = ArrayList::new;
Consumer<String> printConsumer = a1 -> System.out.println(a1);
BiConsumer<Integer, Integer> sumConsumer = (a1, a2) -> System.out.println(a1 + a2);

As for usage, the very basic example would be: Stream#forEach(Consumer) method. It takes a Consumer, which consumes the element from the stream you're iterating upon, and performs some action on each of them. Probably print them.

Consumer<String> stringConsumer = (s) -> System.out.println(s.length());
Arrays.asList("ab", "abc", "a", "abcd").stream().forEach(stringConsumer);

A Supplier is any method which takes no arguments and returns a value. Its job is literally to supply an instance of an expected class. For instance, every reference to a 'getter' method is a Supplier

public Integer getCount(){
    return this.count;
}

Its instance method reference myClass::getCount is an instance of Supplier<Integer>.

A Consumer is any method which takes arguments and returns nothing. It is invoked for its side-effects. In Java terms, a Consumer is an idiom for a void method. 'setter' methods are a good example:

public void setCount(int count){
    this.count = count;
}

Its instance method reference myClass::setCount is an instance of Consumer<Integer> and IntConsumer.

A Function<A,B> is any method which takes an argument of one type, and returns another. This can be referred to as a 'transformation'. The Function<A,B> takes an A and returns a B. Notable is that for a given value of A, the function should always return a specific value of B. A and B can in fact be the same type, such as the following:

public Integer addTwo(int i){
    return i+2;
}

Its instance method reference myClass:addTwo is a Function<Integer, Integer> and a ToIntFunction<Integer>.

A Class method reference to a getter is another example of a function.

public Integer getCount(){
    return this.count;
}

Its class method reference MyClass::getCount is an instance of Function<MyClass,Integer> and ToIntFunction<MyClass>.

Tags:

Java

Java 8