I have simple class CountBarrier
with method count()
and I want to replace something like that:
for (int i = 0; i < 5; i++) {
countBarrier.count()
}
with:
IntStream.range(0, 5).forEach(countBarrier::count);
but to work properly I can write only this :
IntStream.range(0, 5).forEach((i) -> countBarrier.count());
How to manage this issue (don't want to see var i)?
"Doctor, it hurts when I press here!" "Well, stop pressing there then".
It sounds like you wish to write the notion of 'call this method 5 times', as succintly as possible.
Java can do that - and has been capable of doing that for 30 years:
for (int i = 0; i < 5; i++) countBarrier.count();
Exactly like that. No braces. No newline.
Imagine your attempt to use foreach actually worked (it doesn't, and cannot be made to, but let's travel to hypothetical-land and find this code nestled between the unicorns and the rainbows):
IntStream.range(0, 5).forEach(countBarrier::count);
It's longer. Clearly the first is superior then, if we go by golfing standards.
But, that last snippet (already inferior) doesn't even work - as you found it. The problem is, forEach
's purpose is to execute a given consumer (which is a code block that takes 1 parameter and returns nothing) once for each thing in the stream, and the stream element is passed to each invocation. You have a stream of integers here (from 0 to 4, inclusive), so .forEach
's purpose is to run some code for 0, for 1, for 2, for 3, and finally for 4, and that index is handed to the code. Your count
method must therefore take as parameter an int
. It doesn't, hence java refuses to compile this. There is no language feature to tell java: Yeah, whatever, I don't care about the i
just run it - because java is a stickler like that. There is no (easy) fix for this.
Which gets us back to to:
for (int i = 0; i < 5; i++) countBarrier.count();
is the superior way to do this job. So, write that.
No, they aren't. It's somewhat common in style guides to demand you use them, but there's a very very simple solution to that problem: Don't use such needlessly restrictive style guides, and always ask why. Style guides don't come from the mountain; the rules they convey have an underlying intent. The intent of style guide rules in the vein of 'braces for ALL THE THINGS' is primarily that you can easily add extra statements (maybe you want to also emit a log message in addition to invoking count()
, every loop), and partly to avoid really long lines.
Chaining lambdas / stream API has the exact same problem if you just pile it all on. Given your preferred (non-working) syntax, if you want to also emit a log message in addition to calling count, you need to turn this:
IntStream.range(0, 5).forEach(countBarrier::count);
into this:
IntStream.range(0, 5).forEach(() -> {
log.info("Counting!");
countBarrier.count();
});
That is way more rewriting compared to turning this:
for (int i = 0; i < 5; i++) countBarrier.count();
into this:
for (int i = 0; i < 5; i++) {
log.info("Counting!");
countBarrier.count();
}
There are only three options:
.forEach
is a style violation.Long lines? Well, it's obvious that piling on with stream API leads to incredulously long lines so that's clearly a non-starter: You can't use 'but lambdas let me put stuff on one line!' whilst also supporting the notion that all traditional for loops must use braces because otherwise lines get too long.
They definitely aren't. Lambdas in java have no idea if they run 'in context' or 'out of context'. For example, .forEach()
runs 'in context': The call foo.forEach(x -> codeGoesHere)
will finish all loops before that entire statement, lambda and all, 'resolves' and completes. However, that doesn't have to be the case with lambdas. For example, this:
new TreeSet<>(Comparator.comparing(Student::getName))
Never invokes any student object's .getName()
method. That 'code' instead is saved in a field in TreeSet and will be invoked anytime you interact with that treeset. Which could be 5 days from now, in a different thread.
And java doesn't know.
Because of that, java intentionally makes these things non-transparent in lambdas of any stripe:
try {
listOfPaths.forEach(path -> books.append(Files.readString(path)));
} catch (IOException e) { .. }
Is a compiler error, because Files.readString
is declared to throws IOException
- and even though we catch it, javac has no idea if that lambda is run 'in context' or not, and therefore doesn't compile the above. Replace it with a plain jane for loop and that works fine.
Hence, lambdas aren't a replacement for loops / control flow statements like do
, while
, or try
. They are different tools and are good at different things. Use the right tool for the job. Which, for this job, is not lambdas.
You cannot interact with them or even read them from within a lambda. After all, if that code ends up running 5 days from now in a different thread, well, that context is long gone. Yes, we know that it'll run in context, but javac doesn't, and therefore won't let you. No such problem with traditional for loops.
You can break
, continue
, or return
from inside a normal for loop to outside of it. But lambdas cannot do that. Again, same reason. It'd make absolutely no sense at all if the lambda runs out of its context. The thing you are attempting to break/continue/return to is looong gone.
Hence, whilst any and all benefits to using .forEach
style looping are purely based on 'style' (and beauty is in the eye of the beholder. You cannot make rational arguments in favour of finding one thing more beautiful than another) – the benefits of using traditional for loops are objective. They can do more.
forEach
allows the list construct to control the looping behaviourIt's specced specifically to go in list order, sequentially. There is no difference in how it runs now and never will be, as that would be backwards incompatible.
.forEach
lets me omit the variable type, so, it is shorterNot so, as var
exists. This is legal java:
for (var map : listOfMapsOfStringsToLists) { .... }
IntStream
is 'abuse' - the purpose of IntStream is to generate a consecutive sequence of int
values. You have no interest in them (you are merely 'abusing' it to make something be called 5 times, you have no interest specifically in the sequence '0, 1, 2, 3, 4' - you merely have an interest in any sequence of length precisely 5), but because you have them, you can only use ::
syntax to invoke a method that consumes an int. Your count()
method doesn't (and you don't want it to), so you shouldn't be using IntStream. What you'd want instead is something like:
Loops.forEach(5, countBarrier::count);
Unfortunately, java doesn't have this method. But, you can write it yourself, of course! It's easy:
class Loops {
public static void forEach(int count, Runnable r) {
if (i < 0) throw new IllegalArgumentException("i is negative");
for (int i = 0; i < count; i++) r.run();
}
}
A trivial 2 liner.