Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BooleanSupplier usage scenarios

Tags:

java-8

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));
like image 644
Sankalp Avatar asked Jan 20 '18 02:01

Sankalp


People also ask

What is the purpose of BooleanSupplier interface?

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 .

What is a BooleanSupplier?

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.


3 Answers

In theory, the main reason I might use Suppliers 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.

like image 91
fps Avatar answered Dec 10 '22 12:12

fps


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.

like image 37
Eugene Avatar answered Dec 10 '22 11:12

Eugene


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).

  • substitute with a new indirect component on demand without to change the code of the supplier side.
  • make the computation lazily through the indirect component. e.g: the body of lambda of stringEquals doesn't to be executed until it is called in your example.
  • make the final result cachable
  • recording/logging the invocation of the indirect component, and so on...
like image 32
holi-java Avatar answered Dec 10 '22 11:12

holi-java