Thursday 7 December 2023

Is Stream.findFirst() Short-circuited?

So, the assumption is: if I use findFirst on a stream, none of the items in the stream after the first match are evaluated.

I assumed that it was, and it is, but it's always nice to see this verified in a simple test.

  private List<String> list = new ArrayList<>();

  private boolean add(String message, boolean returnValue) {
    list.add(message);
    return returnValue;
  }

  public boolean check() {
    List<Supplier<Boolean>> checks = new ArrayList<>();
    checks.add(super::onLeave);
    checks.add(() -> {
      list.add("First expression");
      return true;
    });
    checks.add(() -> {
      list.add("Second expression");
      return true;
    });
    checks.add(() -> {
      list.add("Third expression");
      return false;
    });
    checks.add(() -> {
      list.add("Fourth expression");
      return true;
    });
    checks.add(() -> {
      list.add("Fifth expression");
      return true;
    });
    checks.add(() -> {
      list.add("Sixth expression");
      return false;
    });
    return checks.stream()
        .filter(t -> t.get().equals(Boolean.FALSE))
        .findFirst()
        .isEmpty();
  }

  @Test
  public void testShortCircuit() {
    assertThat(check()).isFalse();
    assertThat(list)
        .containsExactly("First expression", "Second expression", "Third expression");
  }

As this test passes, it seems that way.

In the very beginning it took some time for me to wrap my head around it, but the operations you define on a stream (.map, .filter, etc.) are not all processed on every item in the stream.

All operations are processed on the first item of the stream, then on the second item of the stream. From this it follows, that a .findFirst() operation will immediately terminate operations if it finds one and the rest of the stream will be ignored.

No comments:

Post a Comment