Java中匿名内部类/Lambda为何允许修改数组元素却符合有效最终规则?
Great question! Let's unpack this by first clarifying what the "effectively final" rule actually enforces, then applying that to your examples.
First: What does "effectively final" mean?
A variable is effectively final if it’s not explicitly modified after its initial assignment—even if you don’t add the final keyword. The compiler only checks that the variable itself doesn’t get reassigned; it doesn’t restrict changes to the contents of objects the variable references.
Why array elements are allowed to be modified
Let’s break down your two examples side by side:
Example 1: Final primitive variable
final boolean run = true; executor.execute(() -> { while (run) { // do something } });
Here, run is a primitive variable marked final—you can’t reassign it to false anywhere after its initial value is set. The Lambda captures this value safely because it’s guaranteed not to change.
Example 2: Array variable (effectively final)
boolean[] runArr = { true }; executor.execute(() -> { while (runArr[0]) { // Fixed the typo here—should reference the array element // do something } }); runArr[0] = false;
The key here is that the runArr variable itself is effectively final—you never reassign it to a new array (like runArr = new boolean[1];). Modifying runArr[0] changes the contents of the array object that runArr points to, but it doesn’t alter the runArr variable’s value (which is the memory address of the array).
The compiler only cares that the variable’s reference doesn’t change, not about the internal state of the object it references. That’s why this operation is allowed.
Why this design makes sense
The effectively final rule exists to prevent subtle bugs from variable capture in Lambdas/anonymous classes—if a variable could be reassigned outside the Lambda, the Lambda might see inconsistent values. But when you modify an object’s internal state (like array elements), that’s a deliberate choice by the developer to share state between threads or scopes.
The compiler doesn’t enforce thread safety here—it’s up to you to handle that! For example, modifying runArr[0] in your executor example could lead to visibility issues (the Lambda thread might never see the updated value) unless you use synchronization or a thread-safe type like AtomicBoolean.
Applying this to your Fibonacci example
Your Supplier for generating Fibonacci numbers uses an array fibs to track the last two values:
int[] fibs = {0, 1}; Stream<Integer> fibonacci = Stream.generate(() -> { int result = fibs[1]; int fib3 = fibs[0] + fibs[1]; fibs[0] = fibs[1]; fibs[1] = fib3; return result; });
This works because fibs is effectively final—you never reassign it to a new array. The Lambda modifies the array’s elements, but that doesn’t violate the rule. This is a common pattern for maintaining state in Supplier implementations when you need to carry values between invocations.
Key takeaway
- The effectively final rule applies to the variable itself, not the objects it references.
- Modifying array elements (or fields of an object) doesn’t reassign the variable, so it’s allowed.
- While the compiler permits this, you need to be cautious about thread safety and visibility when sharing mutable state across scopes or threads.
内容的提问来源于stack exchange,提问作者Spanner0jjm

