4

Inspired by Adam Bien's weblog I wanted to replace a common iteration in Java 7 into a nicer one in Java 8. The older code looked like this:

void repeatUsingJava7(int times) {
    for (int i = 0; i < times; i++) {
        doStuff();
        doMoreStuff();
        doEvenMoreStuff();
    }
}

...which is not too nice. So I replaced it using Adam Bein's example into this:

void repeatUsingJava8(int times) {
    IntStream.range(0, times).forEach(
        i -> {
            doStuff();
            doMoreStuff();
            doEvenMoreStuff();
        }
    );
}

...which is a step in the right direction, but does not make the code much simpler to read, and also introduces an unneeded variable i, as well as an extra pair of curly brackets. So now I'm wondering if there are other ways to write this code which will make it even nicer and easier to read, primarily using Java 8.

Eran
  • 387,369
  • 54
  • 702
  • 768
uzilan
  • 2,554
  • 2
  • 31
  • 46
  • possible duplicate of [Java 8 Iterable.forEach() vs foreach loop](http://stackoverflow.com/questions/16635398/java-8-iterable-foreach-vs-foreach-loop) – Bartosz Bilicki Jan 05 '15 at 12:34
  • I can't see how they are similar: my question is about repeating operations a number of times, while the one you are refering to is about iterating over a list – uzilan Jan 05 '15 at 12:37
  • 1
    The second way of expressing the code is *not introducing* “an unneeded variable i as well as an extra pair of curly brackets”. *Both* code fragments have a variable `i` and *both* have the *same* number of curly brackets. Well, they *are* equivalent… – Holger Jan 05 '15 at 12:49

4 Answers4

5

Just for completeness, here is a solution which doesn’t need the counter variable:

void repeatUsingJava8(int times) {
    Collections.<Runnable>nCopies(times, ()->{
        doStuff();
        doMoreStuff();
        doEvenMoreStuff();
    }).forEach(Runnable::run);
}

It would become more readable if there is only one method to be invoked multiple times as in that case it could be written as, e.g.

void repeatUsingJava8(int times) {
    Collections.<Runnable>nCopies(times, this::doStuff).forEach(Runnable::run);
}

If it has to be Streams, the code above is equivalent to

void repeatUsingJava8(int times) {
    Stream.<Runnable>generate(()->this::doStuff).limit(times).forEach(Runnable::run);
}

However, these alternatives are not really better than the good old for loop. If you consider parallel execution, which is a real advantage of Streams over ordinary for loops, there are still alternatives based on commonly known, approved APIs:

ExecutorService es=Executors.newCachedThreadPool();
es.invokeAll(Collections.nCopies(times, Executors.callable(()->{
    doStuff();
    doMoreStuff();
    doEvenMoreStuff();
})));
Holger
  • 285,553
  • 42
  • 434
  • 765
4

I don't see any advantage in using Streams for this case. Streams are useful for processing of Collections, arrays and other data structures that contain multiple elements of the same type.

Here you are not processing any data, you just repeat the same action multiple times. There is no reason to replace the good old for loop for that purpose.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • I think that this makes the code more expressive, which I belive is what streams and lambdas in java 8 all about. But I agree, the way I wrote the second exapmle does not make the code any simpler, which why I'm asking if there is any other way to write it which does! – uzilan Jan 05 '15 at 12:48
  • 5
    A common danger is that everyone wants to use the new shiny tool for everything, even when the old rusty tool is just fine. When I see the IDE suggest to transform an ordinary for-loop over a collection into a forEach, most of the time, this is the wrong thing to do. Use the new shiny tools where they give you leverage. But there's nothing wrong with for-loops. – Brian Goetz Jan 05 '15 at 17:37
1

As others have pointed out, replacing a simple for-loop with a stream isn't necessarily any better, for this one specific example. However, advantages start to appear if the problem you're trying to solve is more general. For example, instead of repeating some specific code n times, suppose you need to repeat some piece of code that's passed as a parameter? Consider:

void repeat(int count, Runnable action) {
    IntStream.range(0, count).forEach(i -> action.run());
}

Now you can write,

repeat(3, () -> System.out.println("Hello!"));

or perhaps

repeat(4, this::doStuff);

Maybe instead performing a single action n times, you want to perform multiple actions n times. You could do something like this:

void repeat(int count, Runnable... actions) {
    IntStream.range(0, count).forEach(i -> Arrays.asList(actions).forEach(Runnable::run));
}

Then you'd be able to write:

repeat(5, this::doStuff, this::doMoreStuff, this::doEvenMoreStuff);

The repeat implementation is somewhat more concise (perhaps terse) than the conventional Java 7 way:

void repeatOldWay(int count, Runnable...actions) {
    for (int i = 0; i < count; i++) {
        for (Runnable r : actions) {
            r.run();
        }
    }
}

The real advantage comes at the call site, where you can simply pass in a count and one or more lambda expressions or method references, instead of replicating the nested for-loop logic.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
0

You can also use from predicates:

IntStream.range(0, 10).sorted().filter(i -> i > 5).forEach(System.out::println);

sorted and filer is additional .

Tomasz Waszczyk
  • 2,680
  • 5
  • 35
  • 73