Given this situation where a lambda is inside a for loop I would expect the counter i to be effectively final.
The compiler complains that i is not effectively final
As the other answers mention, effectively final
means, that a variable only can be assigned once and will not be reassigned. That's not the case in a for-loop. effectively final
exists, because otherwise developers have to explicitly mark a variable as final
to use it in a lambda.
However, the reason i'm answering is a solution, to write your code without duplicating i
:
IntStream.range (0, x.getBooks().size()).forEach (i -> {
List<Book> books = bookstore.stream()
.filter(c -> c.getAuthors().get(i).equals("xxx"))
.collect(Collectors.toList());
});
You have to remember that a for
loop actually equivalent to :
int i=0;
while(i < x.getBooks().size()){
//execute your block
i++;
}
So you see that i
is declared only once and updated therefore not effectively final.
It's a little unclear the relationship between x.getBooks()
and Book.getAuthors()
but apparently they have the same size? Without a better understanding, I don't think I can show you a better way to do it. But this might also show you that your design is poor.
From the Java Language Specification
The scope of a local variable declared in the
ForInit
part of a basic for statement (§14.14.1) includes all of the following:
- Its own initializer
- Any further declarators to the right in the
ForInit
part of the for statement- The
Expression
andForUpdate
parts of the for statement- The contained Statement
And about effectively final
A local variable or a method, constructor, lambda, or exception parameter is effectively final if it is not declared final but it never occurs as the left hand operand of an assignment operator (§15.26) or as the operand of a prefix or postfix increment or decrement operator
Your i
variable occurs as the operand of postfix increment operator
i++
It is therefore not effectively final and can not be captured by the lambda.
TLDR: i
is not final
because it is modified (i++
) at each iteration of the for
loop.
why i is not effectively final inside the scope of the for loop ?
The for
loop syntax is
for (initialization; termination; increment) {
statement(s)
}
The increment expression is invoked after each iteration through the loop. In your case, increment is i++
so i
is modified after each iteration.
You could confirm this by declaring i
final:
for (final int i = 0; i < x.getBooks().size(); i++) {
}
you will get this compilation error:
The final local variable i cannot be assigned.
It must be blank and not using a compound assignment
is this the simplest workaround ?
In the case of a for
loop: yes.
But you could use a while
loop as shown by @dkatzel or a foreach
:
int i = 0;
for (Book book: x.getBooks()) {
int i2 = i;
...
i++;
}