Variable used in lambda expression should be final or effectively final
JavaLambdaJava Problem Overview
>Variable used in lambda expression should be final or effectively final
When I try to use calTz
it is showing this error.
private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
try {
cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
if (calTz == null) {
calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
}
});
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return null;
}
Java Solutions
Solution 1 - Java
Although other answers prove the requirement, they don't explain why the requirement exists.
The JLS mentions why in §15.27.2:
>The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems.
To lower risk of bugs, they decided to ensure captured variables are never mutated.
Solution 2 - Java
A final
variable means that it can be instantiated only one time.
in Java you can't reassign non-final local variables in lambda as well as in anonymous inner classes.
You can refactor your code with the old for-each loop:
private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
try {
for(Component component : cal.getComponents().getComponents("VTIMEZONE")) {
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
if(calTz==null) {
calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
}
}
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return null;
}
Even if I don't get the sense of some pieces of this code:
- you call a
v.getTimeZoneId();
without using its return value - with the assignment
calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
you don't modify the originally passedcalTz
and you don't use it in this method - You always return
null
, why don't you setvoid
as return type?
Hope also these tips helps you to improve.
Solution 3 - Java
From a lambda, you can't get a reference to anything that isn't final. You need to declare a final wrapper from outside the lamda to hold your variable.
I've added the final 'reference' object as this wrapper.
private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
final AtomicReference<TimeZone> reference = new AtomicReference<>();
try {
cal.getComponents().getComponents("VTIMEZONE").forEach(component->{
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
if(reference.get()==null) {
reference.set(TimeZone.getTimeZone(v.getTimeZoneId().getValue()));
}
});
} catch (Exception e) {
//log.warn("Unable to determine ical timezone", e);
}
return reference.get();
}
Solution 4 - Java
Java 8 has a new concept called “Effectively final” variable. It means that a non-final local variable whose value never changes after initialization is called “Effectively Final”.
This concept was introduced because prior to Java 8, we could not use a non-final local variable in an anonymous class. If you wanna have access to a local variable in anonymous class, you have to make it final.
When lambda was introduced, this restriction was eased. Hence to the need to make local variable final if it’s not changed once it is initialized as lambda in itself is nothing but an anonymous class.
Java 8 realized the pain of declaring local variable final every time a developer used lambda, introduced this concept, and made it unnecessary to make local variables final. So if you see the rule for anonymous classes has not changed, it’s just you don’t have to write the final
keyword every time when using lambdas.
I found a good explanation here
Solution 5 - Java
In your example, you can replace the forEach
with lamdba with a simple for
loop and modify any variable freely. Or, probably, refactor your code so that you don't need to modify any variables. However, I'll explain for completeness what does the error mean and how to work around it.
Java 8 Language Specification, §15.27.2:
> Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted.
Basically you cannot modify a local variable (calTz
in this case) from within a lambda (or a local/anonymous class). To achieve that in Java, you have to use a mutable object and modify it (via a final variable) from the lambda. One example of a mutable object here would be an array of one element:
private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
TimeZone[] result = { null };
try {
cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
...
result[0] = ...;
...
}
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return result[0];
}
Solution 6 - Java
A variable used in lambda expression should be a final or effectively final, but you can assign a value to a final one element array.
private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
try {
TimeZone calTzLocal[] = new TimeZone[1];
calTzLocal[0] = calTz;
cal.getComponents().get("VTIMEZONE").forEach(component -> {
TimeZone v = component;
v.getTimeZoneId();
if (calTzLocal[0] == null) {
calTzLocal[0] = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
}
});
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return null;
}
Solution 7 - Java
if it is not necessary to modify the variable than a general workaround for this kind of problem would be to extract the part of code which use lambda and use final keyword on method-parameter.
Solution 8 - Java
to answer to > https://stackoverflow.com/questions/70733310/variable-used-in-lambda-expression-should-be-final-or-effectively-final-java
to workaround that in not an elegant way , 2 issues : the side effect and the threading issue
final AtomicInteger e = new AtomicInteger(0);
new Thread(() -> {
e.addAndGet(1);
});
to be more precise, i agree is kind of the same but the idea behind using Lambda function is to avoid side affect, and when we are accessing this final reference in the lambda function to populate the value to get the result from outside, we are breaking this concept.
in the oldest post you might want to rewrite like that
cal.getComponents().getComponents("VTIMEZONE").streams().map(v->v.getTimeZoneId().getValue()).collect(Collectors.toList());
and for the threading aspect , we have the same issue with the side effect and additionally you will never know when to access to the Atomic variable to collect the result , you could put a CountDownLatch ... Better to work with CompletableFuture to handle the result and the synchronization aspect