197

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;
}
Jordan Noel
  • 195
  • 2
  • 3
  • 14
user3610470
  • 1,971
  • 2
  • 11
  • 7
  • 8
    You can't modify `calTz` from the lambda. – Elliott Frisch Jan 18 '16 at 22:46
  • 6
    I assumed this was one of those things that just didn't get done in time for Java 8. But Java 8 was 2014. Scala and Kotlin have allowed this for years, so it's obviously possible. Is Java ever planning to eliminate this weird restriction? – GlenPeterson Sep 20 '18 at 22:00
  • 6
    [Here](https://www.bruceeckel.com/2015/10/17/are-java-8-lambdas-closures/) is the updated link to @M.S.Dousti 's comment. – geisterfurz007 Oct 31 '18 at 21:31
  • I think you could use Completable Futures as a workaround. – λraulain Feb 15 '19 at 13:48
  • 1
    One important thing I observed - You can use static variables instead of normal variables (This makes it effectively final I guess) – kaushalpranav Mar 29 '20 at 05:47
  • Here's an example when a variable used in lambda expression **shouldn't** be final: https://stackoverflow.com/q/30360824/9772691 – John McClane May 31 '20 at 00:57

9 Answers9

182

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.

Dioxin
  • 13,888
  • 5
  • 38
  • 80
  • 21
    Good answer +1, and I'm surprised by how little coverage the _reason_ for effectively final seems to get. Of note: A local variable can only be captured by a lambda if it is _also_ definitely assigned before the body of the lambda. Both requirements would seem to ensure that accessing the local variable would be thread safe. – Tim Biegeleisen Sep 14 '18 at 09:09
  • 5
    any idea why this is restricted only to local variables, and not class members? I find myself often circumventing the problem by declaring my variable as a class member... – Maverick Meerkat Mar 05 '19 at 10:15
  • 7
    @DavidRefaeli Class members are covered/affected by the memory model, which if followed, will produce predictable results when shared. Local variables are not, as mentioned in [§17.4.1](https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.4.1) – Dioxin Mar 05 '19 at 14:08
  • 1
    This is a BS reason to handicap lamdas in such a way. If you don't understand multi-threading, you might get bit, but you'll probably also learn something. – Josh M. Mar 11 '21 at 03:49
  • @Dioxin understood, but it's really crappy. There are other ways to burn yourself in Java, but it will let you, e.g.: `Boolean x = null; ... ; if (x) { ... } – Josh M. Mar 11 '21 at 15:54
95

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 passed calTz and you don't use it in this method
  • You always return null, why don't you set void as return type?

Hope also these tips helps you to improve.

srk
  • 4,622
  • 11
  • 59
  • 105
Francesco Pitzalis
  • 1,922
  • 14
  • 20
84

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();
}   
DMozzy
  • 1,009
  • 6
  • 7
  • I was thinking about the same or similar approach - but I would like to see some expert advise/feedback on this answer? – YoYo Dec 05 '17 at 16:03
  • 5
    This code miss an initial `reference.set(calTz);` or the reference must be created using `new AtomicReference<>(calTz)`, otherwise the non-null TimeZone provided as parameter will be lost. – Julien Kronegg May 06 '18 at 20:31
  • 15
    This should be the first answer. An AtomicReference (or similar Atomic___ class) works around this limitation safely in every possible circumstance. – GlenPeterson Sep 20 '18 at 22:21
  • 1
    Agreed, this should be the accepted answer. The other answers give useful information on how to fall back to a non-functional programming model, and on why this was done, but don't actually tell you how to work around the problem! – Jonathan Benn Aug 30 '19 at 20:53
  • 5
    @GlenPeterson and is a terrible decision too, not only it is a lot slower this way, but you are also ignoring the side-effects property that the documentation mandates. – Eugene Oct 23 '19 at 17:13
52

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

Charles Duffy
  • 257,635
  • 38
  • 339
  • 400
Dinesh Arora
  • 1,857
  • 3
  • 22
  • 30
  • Code formatting should only be used for *code*, not for technical terms in general. `effectively final` is not code, it's terminology. See [When should code formatting be used for non-code text?](https://meta.stackoverflow.com/questions/254990/when-should-code-formatting-be-used-for-non-code-text) on [meta]. – Charles Duffy Oct 11 '19 at 15:49
  • (So "the `final` keyword" is a word of code and correct to format that way, but when you use "final" descriptively rather than as code, it's terminology instead). – Charles Duffy Oct 11 '19 at 15:52
13

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];
}
Alexander Udalov
  • 28,964
  • 6
  • 77
  • 65
  • Another way is to use a field of an object. E.g. MyObj result = new MyObj(); ...; result.timeZone = ...; ....; return result.timezone; Note though, that as explained above, this exposes you to thread-safety problems. See https://stackoverflow.com/a/50341404/7092558 – Gibezynu Nu May 13 '20 at 05:56
2

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;
}
Andreas Foteas
  • 343
  • 2
  • 10
  • This is very similar to the suggestion by Alexander Udalov. Apart from that I think this approach is relying on side-effects. – Scratte Apr 08 '20 at 15:55
1

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.

robie2011
  • 3,188
  • 4
  • 20
  • 20
0

to answer to > 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

  • here the functional interface is imposed as a runable , it would have been better to use CompletableFuture – dixseptcent Jan 19 '22 at 12:55
  • not sure the oldest answer is correct , worth to use a stream and map the items in VTimeZone , filter them as non null and collect them in a list – dixseptcent Jan 19 '22 at 12:57
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 19 '22 at 13:24
0

Final Variable: a variable which is declared with the final keyword cannot be modified and will have the same value throughout the application.

Effective Final Variable: a variable which is not explicitly declared with a final keyword but never gets modified throughout the application, although it's not declared as final since the value has not been modified hence compiler treats them just like final and can be termed as Effective Final.

The main reason behind the requirement of final and Effective Final is Lambda Expressions can use local variables defined outside. compiler passes the variable in lambda using pass by value, lambda tries to keep the value as constant because changing the value of a variable with each iteration might result in an incorrect result.

The process of keeping local variables is known as capturing lambdas.

Lambda can capture static variables, instance variables, and local variables, but only local variables must be final or effectively final.

local variables are saved in the stack, each thread has its own call stack, and no thread can access another thread stack, where are static and instance variables are stored on the heap which is shared among all threads.

Check this Variable used in Lambda Expression Should be Final or Effectively Final