Hooking lambda laziness via Supplier/Consumer – Functional style programming – extending API
181. Hooking lambda laziness via Supplier/Consumer
The java.util.function.Supplier is a functional interface capable to supply results via its get() method. The java.util.function.Consumer is another functional interface capable to consume the argument given via its accept() method. It returns no result (void). Both of these functional interfaces are lazy, so it is not quite simple to analyze and understand a code that implies them, especially when a snippet of code implies both of them. Let’s give it a try!Consider the following simple class:
static class Counter {
static int c;
public static int count() {
System.out.println(“Incrementing c from “
+ c + ” to ” + (c + 1));
return c++;
}
}
And, let’s write the following Supplier and Consumer:
Supplier<Integer> supplier = () -> Counter.count();
Consumer<Integer> consumer = c -> {
c = c + Counter.count();
System.out.println(“Consumer: ” + c );
};
So, at this point, what is the value of Counter.c?
System.out.println(“Counter: ” + Counter.c); // 0
The correct answer is: Counter.c is 0. The supplier and the consumer are lazy, so none of the get() or accept() methods was called at their declarations. The Counter.count() was not invoked so far, so Counter.c was not incremented.Here is a tricky one … how about now?
System.out.println(“Supplier: ” + supplier.get()); // 0
We know that by calling supplier.get() we trigger the Counter.count() execution and Counter.c should be incremented and become 1. However, the supplier.get() will return 0.The explanation resides in the count() method at line return c++;. When we write c++, we use the post-increment operation, so we use the current value of c in our statement (in this case, return) and afterward, we increment it by 1. This means that supplier.get() gets back the value of c as 0, while the incrementation takes place after this return, and Counter.c is now 1:
System.out.println(“Counter: ” + Counter.c); // 1
If we switch from post-increment (c++) to pre-increment (++c) then supplier.get() will get back the value of 1 which will be in sync with Counter.c. This is happening because the incrementation takes place before the value is used in our statement (here, return).Ok, so far we know that Counter.c is equal to 1. Next, let’s call the consumer and let’s pass in the Counter.c value:
consumer.accept(Counter.c);
Via this call, we push the Counter.c (which is 1) in the following computation and display:
c -> {
c = c + Counter.count();
System.out.println(“Consumer: ” + c );
} // Consumer: 2
So, c = c + Counter.count() can be seen as Counter.c = Counter.c + Counter.count() which is equivalent to 1 = 1 + Counter.count(), so 1 = 1 + 1. The output will be: Consumer: 2. This time, Counter.c is also 2 (remember the post-increment effect):
System.out.println(“Counter: ” + Counter.c); // 2
Next, let’s invoke the supplier:
System.out.println(“Supplier: ” + supplier.get()); // 2
We know that get() will receive the current value of c which is 2. Afterward, Counter.c becomes 3:
System.out.println(“Counter: ” + Counter.c); // 3
We can continue like this forever, but I think you got the idea of how the Supplier and the Consumer functional interfaces work.