当我考虑使用BooleanSupplier
时,我正在尝试了解用例场景。在其解释中的大多数例子中,我得到的最多。我想了解使用BooleanSupplier
提供的优势比简单的比较更好吗?
String s1 = "ABC";
String s2 = "ABC";
BooleanSupplier stringEquals = () -> s1.equals(s2);
System.out.println(stringEquals.getAsBoolean());
反对这一点 -
System.out.println(s1.equals(s2));
答案 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使用了密钥。