符号”||”(或者”or”)和”Stream.of(..).anyMatch(e -> e==true)”函数在功能上是否等效?

huangapple go评论78阅读模式
英文:

Are the || ('or') symbol and Stream.of(..).anyMatch(e-> e==true) functionally equivalent?

问题

||Stream.of(..).anyMatch(e -> e==true)在相同顺序的条件系列上应用时,功能上是否等效?

任务:对一系列条件运行快速测试,确定是否有任何条件为真。

可能的解决方案:

  • 用“或”符号(||)分隔每个条件
  • 将每个条件包含在Stream.of(..)语句中,该语句附加了.anyMatch(e -> e==true)

关于anyMatch(..)的文档说明:“返回此流的任何元素是否与提供的谓词匹配。如果不必要以确定结果 [强调添加],则可能不会对所有元素进行谓词评估。”

基于这个说明,我的理解是上述两个指示的解决方案在功能上是相同的,因此,如果四个条件中的第二个条件是第一个为true的元素,那么第三个和第四个元素将不会被评估。

然而,在实践中,这似乎并不成立。考虑以下情况,其中itemnullemptytrueUtilMethods.isEmpty(..)是一个自定义的库方法,用于测试给定参数是否为null或空的String(""):

@Override
protected void updateItem(Pair<K, V> item, boolean empty) {
    super.updateItem(item, empty);

    boolean test1 = empty
             || Objects.isNull(item)
             || UtilMethods.isEmpty(item.toString())
             || Objects.isNull(item.getValue());

    boolean test2 = Stream.of(
        empty,
        isNull(item),
        UtilMethods.isEmpty(item.toString()),
        isNull(item.getValue()))
    .anyMatch(e -> e == true);
}

||代码按预期运行。然而,Stream.of(..).anyMatch(..)在到达第三个元素时抛出NullPointerException,因为itemnull

鉴于上面引用的anyMatch(..)文档,这在逻辑上是不合理的,这表明第三个元素甚至不应该被达到。或者我在这里漏掉了什么吗?

谢谢

英文:

Are || and Stream.of(..).anyMatch(e-&gt; e==true) functionally equivalent when applied to the same series of conditions in the same order?

Task: Run a quick test on a series of conditions to determine whether any are true.

Possible solutions:

  • Separate each condition with an ‘or’ symbol (||)
  • Include each condition in a Stream.of(..) statement that is appended with .anyMatch(e-&gt; e==true)

The documentation for anyMatch(..) states, “Returns whether any elements of this stream match the provided predicate. May not evaluate the predicate on all elements if not necessary for determining the result [emphasis added].”

Based on this statement, my understanding is that the two solutions indicated above are functionally the same, so that if the second element in a serious of four is the first element that is true, then elements three and four won’t be evaluated.

In practice, however, this seems not to be true. Consider the following where item is null, empty is true and UtilMethods.isEmpty(..) is a custom library method that tests if a given parameter is null or an empty String (""):

@Override
protected void updateItem(Pair&lt;K, V&gt; item, boolean empty) {
    super.updateItem(item, empty);

    boolean test1 = empty
             || Objects.isNull(item)
             || UtilMethods.isEmpty(item.toString())
             || Objects.isNull(item.getValue());

    boolean test2 = Stream.of(
        empty,
        isNull(item),
        UtilMethods.isEmpty(item.toString()),
        isNull(item.getValue()))
    .anyMatch(e -&gt; e == true);
}

The || code runs as expected. However, the Stream.of(..).anyMatch(..) throws a NullPointerException when the third element is reached because item is null.

This doesn’t make sense in view of the documentation for anyMatch(..) that is referenced above, which suggests that the third element shouldn’t even be reached. Or am I missing something here?

Thanks

答案1

得分: 5

UtilMethods.isEmpty(item.toString()) 在执行 Stream.of() 之前被求值,因此无论您之后是否调用 anyMatch,它都会抛出 NullPointerException

Stream.of() 就像任何方法调用一样,在执行之前会对所有参数进行求值,所以它必须对 UtilMethods.isEmpty(item.toString()) 进行求值。

您可以在一个更简单的代码片段中看到相同的行为:

String s = null;
Stream<Integer> stream = Stream.of(5, s.length());

因此,anyMatch 的文档与您观察到的行为无关。

英文:

UtilMethods.isEmpty(item.toString()) is evaluated before Stream.of() is executed, so it will throw a NullPointerException regardless of whether or not you call anyMatch afterwards.

Stream.of(), just as any method call, evaluates all of its arguments before being executed, so it must evaluate UtilMethods.isEmpty(item.toString()).

You can see the same behavior in a much simpler snippet:

String s = null;
Stream&lt;Integer&gt; stream = Stream.of (5,s.length());

Hence, the documentation of anyMatch is irrelevant to the behavior you observed.

答案2

得分: 4

你的观察是正确的。这两个代码片段的确不相同。|| 所具有的重要行为,Stream 并不具备,就是 || 是短路操作。当第一个操作数为 true 时,|| 会立即停止评估。但对于你所写的流操作并不成立。在流运行之前,所有四个表达式:

empty,
isNull(item),
UtilMethods.isEmpty(item.toString()),
isNull(item.getValue())

都会被评估。是的,anyMatch 是惰性的,但 of 不是。明白我的意思吗?如果前一个元素已经评估为真,anyMatch 将不会为每个元素评估 e == true。但这在 of 中不适用。

要复制 || 的行为,你需要将这四个表达式包装成 Supplier<Boolean>,并在 anyMatch 中进行评估:

boolean test2 = Stream.<Supplier<Boolean>>of(
    () -> empty,
    () -> isNull(item),
    () -> UtilMethods.isEmpty(item.toString()),
    () -> isNull(item.getValue()))
    .anyMatch(Supplier::get);
英文:

Your observation is correct. The two code snippets are indeed not the same. The important behaviour that || does, that Stream does not, is that || is short circuiting. When the first operand is true, || stops evaluating immediately. This is not true for the stream operation you have written. All 4 expressions:

empty,
isNull(item),
UtilMethods.isEmpty(item.toString()),
isNull(item.getValue())

Are evaluated before the stream even runs. Yes, anyMatch is lazy, but of is not. See what I mean? anyMatch will not evaluate e == true for every element if a previous element evaluated to true already. This does not apply to of.

To replicate the || behaviour, you'd have to wrap those 4 expressions into Supplier&lt;Boolean&gt;s, and evaluate them in anyMatch:

boolean test2 = Stream.&lt;Supplier&lt;Boolean&gt;&gt;of(
    () -&gt; empty,
    () -&gt; isNull(item),
    () -&gt; UtilMethods.isEmpty(item.toString()),
    () -&gt; isNull(item.getValue()))
    .anyMatch(Supplier::get);

答案3

得分: 3

正如您可能早已知道的那样,|| 使用了短路求值,也就是说,如果第一个条件为真,第二个条件将不会被评估。这个事实可以避免在第一个条件中出现NullPointerException

流水线在某种程度上有类似的行为:最终的anyMatch只会从流中获取足够的元素来确定是否存在匹配项。因此,你可能会对出现异常感到惊讶。虽然 Eran 的解释是正确的:Stream.of() 的所有参数在调用of创建流之前都会被评估。这会引发异常。换句话说,流从未被创建。因此,在这种情况下,流中的评估顺序变得不相关。

如果我们暂时忘记这是一个流操作,只将其视为 Java 方法调用,那么这一点可能更加清楚。您的流代码中调用的结构与以下代码类似,只是我稍微简化了一下:

SomeClass.foo(ref.bar()).baz();

这个表达式的求值顺序由 Java 决定:

  1. ref.bar() 被评估以获取传递给 foo() 的参数;
  2. 调用 foo()
  3. 在从 foo() 返回的对象上调用 baz()

然而,如果 refnull,第一步将抛出 NullPointerException,并且步骤 2 和 3 将不会执行。因此,无论 foo() 还是 baz() 都无法改变评估顺序,也无法防止异常的发生。类似地,在您的代码中,流水线无法防止在调用of()创建流之前对所有传递给 Stream.of() 的参数进行评估。

关于评估顺序的规则早在 Java 引入流库之前就已经制定,并且与其他编程语言类似。在 Java 8 引入流之时,他们明智地没有对这些规则进行根本性的改变。在他的答案中,Sweeper 展示了一种获得您预期的评估顺序的好方法。

关于短路求值的解释可以在许多地方找到。其中之一在这里:What is the difference between the | and || or operators?

英文:

As you may long know, || uses shortcut evaluation, that is, if the first item it true, the second is never evaluated. This fact saves you from a NullPointerException in hte first case.

A stream pipeline has a similar behaviour: the final anyMatch only pulls enough elements from the stream to determine whether there is a match. So it may surprise that you get the exception. The explanation of Eran is correct, though: All the arguments of Stream.of() are evaluated before of is called to create the stream. This causes the exception. In other words, the stream never gets created. So the evaluation order in the stream never gets relevant in this case.

That this must be so is probably clearer to see if for a moment we forget that this is a stream operation and just look at it as Java method calls. The structure of the calls in your stream code is similar to the following, only I have simplified it a bit:

SomeClass.foo(ref.bar()).baz();

The sunshine order of evaluation of this expression is dictated by Java:

  1. ref.bar() is evaluated to get the argument to pass to foo();
  2. foo() is called;
  3. baz() is called on the object returned from foo().

However if ref is null, the first step throws a NullPointerException and steps 2 and 3 are never performed. So there is nothing foo() nor baz() can do to change the order of evaluation nor to prevent the exception from happening. Similarly in your code there is nothing the stream pipeline can do to prevent all arguments to Stream.of() to be evaluated before of() is called to create the stream.

The rules about the order of evaluation were laid down in Java long before streams were introduced in the library and are similar to other programming languages. They wisely did not change them radically when introducing streams in Java 8. Sweeper in his answer shows a nice way to obtain the evaluation order you had expected.

Shortcut evaluation is explained in many places. One is here: What is the difference between the | and || or operators?

huangapple
  • 本文由 发表于 2020年3月15日 19:18:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/60692278.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定