Scenarios for using BooleanSupplier

I am trying to understand usage scenarios when I should use BooleanSupplier . From most of the examples in his explanation, I get this the most. I want to understand what advantage BooleanSupplier , than a simple comparison?

  String s1 = "ABC"; String s2 = "ABC"; BooleanSupplier stringEquals = () -> s1.equals(s2); System.out.println(stringEquals.getAsBoolean()); 

how is it possible -

  System.out.println(s1.equals(s2)); 
+5
source share
5 answers

In theory, the main reason I could use Supplier as a whole is, as @Eugene says in his answer , to delay execution. In practice, however, I never had a reason to use BooleanSupplier specifically. Moreover, it is very difficult for me to come up with a use case in real life ...

Despite this, I think it might seem typical to use a Supplier<String> . Hope this can shed light on the use of suppliers in general.

A typical example is logging. Suppose you need to register the result of a very expensive calculation that returns a value, but only when the log level is set to DEBUG . Let's say that this very expensive calculation is represented by the veryExpensive() method, which returns a value (the return type is not important for the example).

The traditional use pattern is to use an if , so we do a very expensive calculation when the DEBUG log level is turned on:

 if (logger.isDebugEnabled()) { logger.debug("veryExpensive() returned: " + veryExpensive()); } 

This works as expected, because if the log level is set to, for example, INFO , we will never call veryExpensive() . But now imagine that you have the same pattern repeating throughout your code. Pretty nice, hmm? A simple task, such as logging, has polluted all your code with an if ... application (And I do not invent it, I really saw this template many times).

Now think about what happens if logger.debug accepts a Supplier<String> instead of a simple String value. In this case, we no longer need the if , since the logic for extracting the String value into the log will now be in the implementation of the logger.debug method. Now use pattern:

 logger.debug(() -> "veryExpensive() returned: " + veryExpensive()); 

Where () -> "veryExpensive() returned: " + veryExpensive() is a Supplier<String> .

This works very well because the execution of veryExpensive() delayed until the logger.debug method logger.debug needs to register the String returned by Supplier<String> , and this will only happen if the DEBUG log level is enabled.

+6
source

This is just a Supplier specialization, so you should really wonder why you need a Supplier at all, and your very simple example does not show the need. This is necessary for at least two reasons, in my opinion:

The first is to delay execution. Think Optional.orElseGet(Supplier...) vs Optional.get() . You might think that they are the same, but the first is only performed if necessary. Now think about the case when the returned object is expensive to calculate (for example, a series of database calls), which one would you choose? Perhaps it should be orElseGet through the supplied Supplier .

Secondly, to create an object all the time by calling supply , I mean that this name Supplier occurs to indicate the values. Inside the Stream API they are used when you need to return a new object, for example, when merging elements in parallel, each stream will receive its own object holder. For example, see:

 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 will take the first argument as Supplier , the same thing will happen in Collectors.toList (look at the implementation) or Collectors.toMap , etc.

One example we use in the production process is to return to the caller a Supplier<Something> instead of Something . When the caller calls get , I can freely do what I want to return Something - we usually cache objects, for example.

+3
source

The basic idea is to use a functional interface ( BooleanSupplier here) instead of using hard code to hide information . let the rest of the code be invisible to you except stringEquals.getAsBoolean() , so we can no longer know the implementation of stringEquals .

The advantages of the functional interface are the use of decoupling the supplier from the client code, for example: to get the final result of stringEquals in your sample code, the client code must know s1 and s2 , but the supplier or consumer side of the functional interface, not knowing about it. due to the implementation, it is encapsulated on the client side. this allows the supplier side to fulfill its internal task, not knowing how customers implement it. for more details you can see the Separation of problems .

On the other hand, it makes no sense to define an instance of a functional interface and immediately call it in the same code block.

Let's look at another specific example of Objects#requireNotNull as the 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 use indirect components (there is a functional interface here).

  • replace the new indirect component on demand without changing the vendor side code.
  • make the calculation lazily through an indirect component. for example: lambda body stringEquals not executed until it is called in your example.
  • make the final result cachable
  • recording / logging an indirect component call, etc.
+3
source

In this particular case, it makes no sense:

 BooleanSupplier stringEquals = () -> s1.equals(s2); System.out.println(stringEquals.getAsBoolean()); 

instead of just:

 System.out.println(s1.equals(s2)); 

Therefore, no advantages, in fact, the latter approach is perhaps easier to read and has less code.

A BooleanSupplier , as well as any other functional interface, is just a criterion to say that "the structure of the reference function must have x number of arguments and may or may not return a value"

However, BooleanSupplier is just a derivative of the Supplier primitive created by Boolean production, so its task will ultimately return the value to the caller (for example, the getter method).

Since BooleanSupplier is a functional interface, you can pass it as a parameter to parameterize behavior to another method, make it as a return type to a method, which can then be used elsewhere; and also use it as the target type for a method that takes no arguments, possibly by executing some logic and returning the result.

When used wisely and, if necessary, it can bring good benefits.

+1
source

Here is an example focused on the BooleanSupplier.

I have a method:

 static WeakHashMap<K, Reference<T>> cachemap = new WeakHashMap<>(); static T getCached(K key, BooleanSupplier isOK) { synchronized(cachemap) { T item = cachemap.computeIfAbsent(key, k-> new SoftReference<>(new T(k, isOK.getAsBoolean()))).get(); if (item == null) // Could be if reference has been GC'ed { item = new T(key, isOK.getAsBoolean()); cachemap.put(key, new SoftReference<>(item)); } return item; } } 

The reason I use BooleanSupplier is because the definition of "isOK" is often a complex boolean expression that involves a complex match with a regular expression. This function basically retrieves the cached value if it was created earlier, and it was not garbage collected, and only performs the “expensive” BooleanSupplier function if it is necessary to create an element. My code actually uses RedundantLock, but it is easier to read. This is also a good example of using WeakHashMap as a cache, where T uses the key.

0
source

Source: https://habr.com/ru/post/1274828/


All Articles