3
\$\begingroup\$

I want to replace a for loop with a break/return condition inside with a lambda. I think I have a good replacement, but I want to be sure.

Having this method

boolean tryVariousTimesSomething() {
  // whatever, like random() % 3 == 0
}

Cosider this code

void tryVariousTimesSomethingWithFor() {
  boolean result = false;
  for(int i= 0; i < 5; i++) {
    if (trySomething()) {
      result = true;
      break;
    }
  }
  LOGGER.log("Tried with result: " + result);
}

Will be correctly replaced by this code?

void tryVariousTimesSomethingWithFor() {
  boolean result = IntStream.range(0,5).
                anyMatch(i -> trySomething());
  LOGGER.log("Tried with result: " + result);
}

Any alternatives?

I tried this code, and it seems to work, but I want a second opinion and to know if there are any alternatives.

I know there are libraries that help you trying and repeating like Failsafe and Resilience4j, but this is a simpler case.

\$\endgroup\$
2
  • \$\begingroup\$ 1. Fixed result = result = , a typo. 2. Yeah, Failsafe and Resilence4J may be overkill in ths scenario \$\endgroup\$ Commented May 17 at 20:21
  • \$\begingroup\$ While it is sometimes necessary to remove details to reduce the length of the code or to hide proprietary details, this one I think went so far to remove every piece of detail necessary to give a meaningful answer. The code snippet is just too abstract to be evaluated. \$\endgroup\$ Commented May 20 at 6:36

1 Answer 1

4
\$\begingroup\$

Loop vs Stream

Before addressing your question, I want to emphasize that loops and Streams are not always interchangeable.

In some cases, loops can be converted into Streams. But it doesn't mean that a Stream is just a fancy form of a loop. These are elements of two different programming paradigms.

With loops, you have a tight full control over the iteration process. Break earlier whenever you need, even jump using labels (although it's not a recommended practice, to use labeled statements). You dictate how to traverse, loops are external iterators. Also, side effects are good buddies of loops. It's very common to manipulate with external state, mutate elements of the structure you're iterating over, etc. That's the world of imperative programming, dictatorship expressed in the form of code.

On the other hand, Stream is an internal iterator, i.e. you're not managing the process of the iteration directly. Also, since streams are functional programming constructs you're not supposed to mutate stream elements (yes, it's a side effect), manipulate with the state outside the stream, etc.

Side effects, especially in the intermediate operations, are discouraged by the Stream API documentation:

Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement, as well as other thread-safety hazards.

...

Many computations where one might be tempted to use side-effects can be more safely and efficiently expressed without side-effects, such as using reduction instead of mutable accumulators. However, side-effects such as using println() for debugging purposes are usually harmless. A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care.

With streams it's not always possible to terminate the execution early (that's a downside of relinquishing the total control over the iterations process). For instance, collect() operation lacks short-circuiting.

To summarize, both loops and streams have their benefits and limitations. Your job is to pick the right tool for the problem at hand. Some problems are easier to implement using imperative logic.

Now, regarding the question of refactoring the for-loop you shared into a stream.

The logic of this for-loop can indeed be expressed as a stream correctly and without the loss of short-circuiting by using anyMatch() operation.

IntStream.range(0, 5).anyMatch(i -> trySomething());

That's the cleanest way to achieve this (let's blind on the use of the magic number 5 for now), this stream statement is concise and bears low cognitive load.

"if there are any alternatives" - yes, but these are not better alternatives.

In a hypothetical scenario, you can come with one of them (instead of using anyMatch). I'll show one as an illustration:

!IntStream.range(0, 5).noneMatch(i -> trySomething());

This example is an exact equivalent. Pause and observe.

Did you spot any problem, why it's not a better alternative? It's as concise as your version with anyMatch, but it's way more difficult to comprehend (yes, conciseness in not everything). Always, try to criticize your code, question yourself if it's clear, look for convoluted logic, redundancy, etc.

Avoid magic numbers

Since this snippet is a part of retry logic in a Microservice (I assume that because you mentioned Resileance4J), then the magic number 5 should denote the maximum number of retry attempts.

It worth defining it a static final field to make it an explicit notion:

private static final int RETRY_THRESHOLD = 5;

void tryVariousTimesSomethingWithFor() {
    boolean result = IntStream.range(0, RETRY_THRESHOLD)
        .anyMatch(i -> trySomething());
    
    LOGGER.log("Tried with result: " + result);
}

Code formatting

  • Dot operator . is meant to be placed before a method call, don't leave it dangling at the end of the previous line.

  • Use standard indentations.

  • Don't skimp on white-spaces (there are some missing in the for-loop).

  • If you're using IntelliJ, shortcut for reformatting Ctrl + Alt + L (note, that it's a good practice to select a certain code element, rather than reformatting the whole file).

\$\endgroup\$
2
  • \$\begingroup\$ Wow ! Thanks for a thorough response \$\endgroup\$ Commented May 18 at 6:35
  • 2
    \$\begingroup\$ While this is a very informative answer, it isn't clear that the question should be answered rather than closed. The code looks hypothetical rather than like actual code from a project, and in that case it can be closed as Missing Review Context. \$\endgroup\$
    – pacmaninbw
    Commented May 19 at 13:37

Not the answer you're looking for? Browse other questions tagged or ask your own question.