Java Scope, Part 3 — Loops, Lambdas, and Streams
Where variable rules get a little stricter
This is part three in the series on Java scope. The first post looked at how braces affect what sticks around inside a method. The second one covered methods, fields, and what actually happens when you start using this
. Now we’re getting into loops, lambdas, and streams. This is where things start to shift once you're past the basics.
Lambdas and streams feel clean to write, but the way they handle variables comes with some extra rules you don’t run into in regular blocks.
If you want to start from the beginning, here’s part one:
Loop Variables Get Recycled
I covered this back in Part 1, where loop variables like i
only live inside the loop block and reset with each pass. But this is where things change a bit. If you try to grab that loop variable inside a lambda, it blows up if the value changes.
That won’t compile. The compiler will complain that i
is accessed from within a lambda and needs to be final or effectively final. The reason is that i
changes every iteration, so it’s not stable enough to be captured.
To fix it, store the current value in a separate variable inside the loop:
Now each lambda has its own copy to work with. The compiler is happy, and so is your output.
Final and Effectively Final
Java only lets lambdas use variables that don’t change after they’ve been set.
That doesn’t work because you're trying to change count
inside the lambda. Once a variable gets reassigned, Java treats it as unstable and says you can’t use it in there.
This happens because lambdas don’t actually grab the variable itself. They take whatever value the variable had when the lambda was built. If that value isn’t locked in, Java can’t guarantee the lambda will behave correctly.
If you do need to update something while looping, you can use a wrapper object instead:
That works because the variable total
never changes, only the number inside it does. You’re not reassigning it, you’re just calling a method on the same object. Java’s fine with that.
Streams Keep Their Scope Tight
Streams work best when they only use variables that stay the same. If you try to use something that changes, Java steps in and stops it.
This works. You’re passing in a value that hasn’t been touched since it was created.
Now watch what happens here:
This doesn’t compile. Changing filter
means it’s no longer eligible to be used inside the lambda. Java needs to know that any variable used in a lambda won’t change, and once you reassign it, that rule’s broken.
You can fix this by assigning the final value before the stream runs:
That keeps everything stable. The lambda can safely use filterValue
because it’s never reassigned.
Conclusion
Lambdas and streams come with a few extra rules, but they’re not hard once you’ve seen them. If a variable doesn’t change, you’re usually in the clear. If it does, you’ll run into problems fast. Same goes for loop variables, they’re scoped to each pass and don’t stick around.
Once this stuff clicks, it’s easier to spot what’s allowed and what’s off-limits.
Next up, I’ll walk through how scope works in try
, catch
, and finally
blocks in more detail. They look familiar, but a few things don’t work the way you’d expect.
If you’re following along or just like posts that break this kind of thing down, feel free to subscribe.