227

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
  • 220
  • 2
  • 4
  • 14
user3610470
  • 2,271
  • 2
  • 12
  • 7
  • 10
    You can't modify `calTz` from the lambda. – Elliott Frisch Jan 18 '16 at 22:46
  • 7
    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

222

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.


This also applies for anonymous inner classes

Vince
  • 14,470
  • 7
  • 39
  • 84
  • 24
    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
  • 8
    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
  • 11
    @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) – Vince 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) { ... } <-- NPE` – Josh M. Mar 11 '21 at 15:54
  • Also note that with anonymous inner class (the effective predecessor to lambdas introduced in Java 8), non-final local variables cannot be accessed either. – wlnirvana Jun 10 '22 at 04:27
104

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,857
  • 12
  • 65
  • 109
Francesco Pitzalis
  • 2,042
  • 1
  • 16
  • 20
101

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,179
  • 1
  • 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
  • 16
    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
  • 7
    @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
59

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
  • 280,126
  • 43
  • 390
  • 441
Dinesh Arora
  • 2,115
  • 3
  • 24
  • 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
  • 31,429
  • 6
  • 80
  • 66
  • 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
3

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
  • 422
  • 3
  • 11
  • 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
  • @Scratte can you post the java working example with public static void main method for above example – deepakl.2000 Jul 04 '22 at 18:22
  • @deepakl.2000 That wouldn't add anything new compared to this post. It would get deleted. – Scratte Jul 04 '22 at 19:23
  • @Scratte how should i execute the code in this post. Please advice – deepakl.2000 Jul 05 '22 at 03:35
  • @deepakl.2000 This isn't the place, really. [How to call a method in Java](https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html): Put it in class, call it from a main method with the two parameters (that you create). Make it `static`, if you don't want to insatiate an object. Modify it to return `calTzLocal[1]` if all goes well. – Scratte Jul 05 '22 at 07:50
  • @Scratte i have done it, i am getting errors – deepakl.2000 Jul 05 '22 at 11:02
2

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
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,678
  • 4
  • 21
  • 20
0

You can't re-assign the variable with new reference inside a lambda expression which is coming from outside scope of lambda.But you can certainly modify existing state of the object.So instead re-assigning 'calTz' to new reference.You can call setter methods on it to change its internal state.So this will work(if your VtimeZone is mutatble only):

    calTz=new TimeZone();    
cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
                         VTimeZone v = (VTimeZone) component;
                    v.getTimeZoneId();
                    
                        calTz.setTimeZoneId("some value");
                    
                })

.But this is not a good practice. Above code can also be replaced by.

        if(calTz == null){
    calTz=new TimeZone();
cal.getComponents().getComponents("VTIMEZONE").get(0).setTimeZoneId("some value");}
Kumar Shanoo
  • 125
  • 4