BooleanSupplier使用场景

时间:2018-01-20 02:35:03

标签: java-8

当我考虑使用BooleanSupplier时,我正在尝试了解用例场景。在其解释中的大多数例子中,我得到的最多。我想了解使用BooleanSupplier提供的优势比简单的比较更好吗?

    String s1 = "ABC";
    String s2 = "ABC";

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

反对这一点 -

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

5 个答案:

答案 0 :(得分:5)

理论上,正如@Eugene在his answer所说的那样,我一般可以使用Supplier的主要原因是推迟执行。但在实践中,我从来没有任何理由专门使用BooleanSupplier。更重要的是,我发现很难想出真实的使用场景......

尽管如此,我认为值得展示Supplier<String>的典型用法。我希望这可以为一般供应商的使用提供一些启示。

典型的例子是记录。假设您必须记录返回值的非常昂贵的计算结果,但仅当日志级别设置为DEBUG时。让我们说这个非常昂贵的计算由方法veryExpensive()表示,它返回一个值(返回类型对于示例来说并不重要)。

传统的使用模式是使用if语句,因此我们只在启用DEBUG日志级别时执行非常昂贵的计算:

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

这可以按预期工作,因为如果将日志级别设置为例如INFO,我们永远不会调用veryExpensive()。但现在想象一下,你的代码遍布了同样的模式。太好了,呃?一个简单的任务(如日志记录)已经用if语句污染了所有代码...(而且我没有发明这个,我实际上已经多次看到这种模式)。

现在想想如果logger.debug接受Supplier<String>而不是普通String值会发生什么。在这种情况下,我们不再需要if语句,因为将String值提取到日志的逻辑现在将驻留在logger.debug方法中实现。使用模式现在是:

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

() -> "veryExpensive() returned: " + veryExpensive()Supplier<String>

这非常有效,因为veryExpensive()的执行推迟到logger.debug方法需要实际记录String返回的Supplier<String>,这只会发生如果启用了DEBUG日志级别。

答案 1 :(得分:3)

这只是Supplier的专业化,因此你应该真正质疑为什么你需要一个Supplier 而你的非常简单的例子不要表明需要一个。在我看来,这至少需要两个原因:

首先,是推迟执行。想一想Optional.orElseGet(Supplier...) vs Optional.get()。您可能认为它们是相同的,但第一个仅在需要时执行。现在想想返回的Object计算成本很高的情况(比如一系列数据库调用),您会选择哪一个?可以说它应该是orElseGet来自提供的Supplier

第二个是通过调用supply始终生成一个对象,我的意思是名称Supplier来自提供值。在Stream API内部,当您需要返回一个新的Object时使用它们,例如当合并 parallel 中的元素时,每个Thread都会获得它自己的Objects对象。例如,请查看:

public static<T, R> Collector<T, R, R> of(
       Supplier<R> supplier,
       BiConsumer<R, T> accumulator,
       BinaryOperator<R> combiner...

了解自定义Collector.of如何将第一个参数视为Supplier,同样的事情会发生在Collectors.toList(查看实现中)或Collectors.toMap等。

我们在制作中使用它的一个示例是返回调用者Supplier<Something>而不是Something。当调用者调用get时,我可以自由地做任何我想要返回的事件Something - 例如,我们通常会缓存对象。

答案 2 :(得分:3)

使用功能接口(此处为BooleanSupplier)而不是使用硬代码的基本思想是information hiding。让我们说除了stringEquals.getAsBoolean()之外,其他代码是不可见的,所以我们不能再知道stringEquals的实现了。

使用功能接口的好处是来自客户端代码的decoupling供应商,例如:为了在示例代码中获得stringEquals的最终结果,客户端代码需要知道{ {1}}&amp; s1,但功能接口方的供应商或消费者不知道它。因为实现是封装在客户端。这让供应方完成其内部任务,而不知道客户如何实现它。有关详细信息,请参阅Separation of Concerns

另一方面,定义一个功能接口实例并立即在同一代码块中调用它是没有意义的。

让我们看一下s2的另一个具体例子如下代码:

Objects#requireNotNull

我们可以从间接组件(此处的功能界面)中受益。

  • 根据需要替换新的间接组件,而不更改供应商方的代码。
  • 通过间接组件懒惰地进行计算。例如: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; } 的lambda体在你的例子中被调用之前不会被执行。
  • 使最终结果可缓存
  • 记录/记录间接组件的调用,依此类推......

答案 3 :(得分:1)

在这种特定情况下,没有必要这样做:

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

而不只是:

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

因此没有任何好处,事实上,后一种方法可以说更容易阅读并且代码更少。

BooleanSupplier以及任何其他功能接口只是一个标准来说明&#34;要引用的函数的结构必须有x个参数,可能会也可能不会返回值& #34;

也就是说,BooleanSupplier只是一个产生布尔值的Supplier原始特化,因此,它的工作最终是向调用者提供一个值(如getter方法)

因为BooleanSupplier是一个功能接口,你可以将它作为一个行为参数化传递给另一个方法,将它作为一个返回类型,然后你可以在别处使用它;以及将它作为一个目标类型用于一个方法,该方法不会使args执行某些逻辑并返回结果。

如果明智地使用,并且在适当的情况下这可以带来好处。

答案 4 :(得分:0)

这里是一个专注于BooleanSupplier的示例。

我有一个方法:

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;
    }
}

我之所以使用BooleanSupplier是因为通常情况下,“ isOK”的确定是一个复杂的布尔型语句,其中包括一个复杂的Regex匹配项。如果以前创建了该缓存值,并且尚未进行垃圾回收,则该函数基本上会检索该缓存值,如果需要创建该项目,则仅执行“昂贵”的BooleanSupplier函数。我的代码实际上使用RedundantLock,但这更易于阅读。这也是将WeakHashMap用作缓存的一个很好的例子,其中T使用了密钥。