I'm trying to understand the use case scenarios when I should consider using BooleanSupplier
. Of most of the examples in its explanation, I get this one the most. I want to understand what advantage does using BooleanSupplier
provide me than the simple comparison?
String s1 = "ABC";
String s2 = "ABC";
BooleanSupplier stringEquals = () -> s1.equals(s2);
System.out.println(stringEquals.getAsBoolean());
as opppsed to this-
System.out.println(s1.equals(s2));
Interface BooleanSupplier This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference. Represents a supplier of boolean -valued results. This is the boolean -producing primitive specialization of Supplier .
BooleanSupplier is a functional interface defined in the "java. util. function" package. This interface can be used as an assignment target for a lambda expression or method reference. BooleanSupplier interface has only one method getAsBoolean() and returns a boolean result, true or false.
In theory, the main reason I might use Supplier
s in general is, as @Eugene says in his answer, to defer execution. In practice, though, I've never had any reason to use BooleanSupplier
specifically. What is more, I'm finding it really difficult to come up with a real life usage scenario...
Despite this, I think it might be worth showing a typical usage of Supplier<String>
. I hope this could shed some light on the use of suppliers in general.
The typical example would be logging. Suppose you have to log the result of a very expensive computation that returns a value, but only when log level is set to DEBUG
. Let's say that this very expensive computation is represented by the method veryExpensive()
, which returns a value (the return type is not important for the sake of the example).
The traditional usage pattern is to use an if
statement, so we only execute the very expensive computation when DEBUG
log level is enabled:
if (logger.isDebugEnabled()) {
logger.debug("veryExpensive() returned: " + veryExpensive());
}
This works as expected, because if the log level is set to e.g. INFO
, we'll never invoke veryExpensive()
. But now imagine you have this same pattern repeated all over your code. Pretty nice, uh? A simple task such as logging has polluted all your code with if
statements... (And I'm not inventing this, I've actually seen this pattern a lot of times).
Now think what would happen if logger.debug
accepted a Supplier<String>
instead of a plain String
value. In this case, we wouldn't need the if
statement any more, because the logic to extract the String
value to log would now reside in the logger.debug
method's implementation. The usage pattern would now be:
logger.debug(() -> "veryExpensive() returned: " + veryExpensive());
Where () -> "veryExpensive() returned: " + veryExpensive()
is a Supplier<String>
.
This works very well because the execution of veryExpensive()
is deferred until the logger.debug
method needs to actually log the String
returned by the Supplier<String>
, and this will only happen if the DEBUG
log level is enabled.
That is simply a specialization of a Supplier
, as such you should really question why you need a Supplier
at all and your very simple example don't show the need for one. This is needed for at least two reasons in my opinion:
First, is to defer the execution. Think Optional.orElseGet(Supplier...)
vs Optional.get()
. You might think that they are the same, but the first one is only executed if needed. Now think of the case when the returned Object is expensive to compute (like a series of DB calls for example), which one would you choose? Arguably it should be orElseGet
via the supplied Supplier
.
The second one is to produce an object all the time via the call to supply
, I mean this is the name Supplier
comes from, to supply values. Inside Stream API
these are used when you would need to return a new Object, for example when merging elements in parallel, each Thread would get it's own holder of Objects. For an example, look at :
public static<T, R> Collector<T, R, R> of(
Supplier<R> supplier,
BiConsumer<R, T> accumulator,
BinaryOperator<R> combiner...
See how the custom Collector.of
would take the first argument as a Supplier
, same thing would happen in Collectors.toList
(look under the implementation) or Collectors.toMap
, etc.
One example we use it in production is to return to the caller a Supplier<Something>
instead of Something
. When the caller calls get
I am free to do whatever I want to return Something
- we usually cache Objects for example.
The basic idea of use functional interface (BooleanSupplier
in here) rather than use hard code is information hiding. let's say the rest of code is invisible from you except stringEquals.getAsBoolean()
, so we can't know the implementation of stringEquals
any more.
The benefits of use functional interface is decoupling the supplier from the client code, e.g: in order to get the final result of the stringEquals
in your example code, the client code need to know s1
& s2
, but the supplier or the consumer of the functional interface side without to know it. because of the implementation is encapsulated at the client side. this let the supplier side to complete its internal task without to know how the clients implements it. for more details, you can see Separation of Concerns.
On the other hand, it is meanless that define a functional interface instance and call it immediately in the same code block.
Let's see another concrete example of Objects#requireNotNull
as following code:
public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
if (obj == null)
throw new NullPointerException(messageSupplier.get());
// ^
// the `requireNotNull` method doesn't know how to create a diagnostic message
return obj;
}
We can benefits from the indirect components (functional interface in here).
stringEquals
doesn't to be executed until it is called in your example.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With