Monday, 21 October 2019

How to Resolve Local Variable Defined in an Enclosing Scope Must be Final or Effectively Final Error

This post talks about how to resolve "local variable defined in an enclosing scope must be final or effectively final" error while trying to write a lambda expression in Java.

Let's first get some background on what is effectively final; that will help you to get an idea why this error is coming.

Effectively Final in Java

When a lambda expression uses an assigned local variable from its enclosing space there is an important restriction.
A lambda expression may only use local variable whose value doesn't change. That restriction is referred as "variable capture" which is described as; lambda expression capture values, not variables. The local variables that a lambda expression may use are known as "effectively final".

An effectively final variable is one whose value does not change after it is first assigned. There is no need to explicitly declare such a variable as final, although doing so would not be an error. Since there is no need to explicitly declare such a variable as final thus the name effectively final. If there is an attempt to change such a variable, anyway compiler will throw an error.

Let's see it with an example. Here I have a functional interface IFunc which has a single abstract method display. Since it has a single abstract method it is a functional interface and lambda expression can be used to implement this functional interface. Just to make it clear I have also used the @Functional interface annotation.

In the code lambda expression that implements the display method of the interface just prints the value of the local variable on the console. It can be noted that there is no need to declare variable i as final which was a requirement before Java 8 (in case of anonymous class).

@FunctionalInterface
interface  IFunc{
    void display();
}

public class InnerDemo {
    
    public static void main(String[] args) {
        int i = 7;
        // lambda expression that implements the display method 
        // of the IFunc functional interface 
        IFunc ifunc = ()-> System.out.println("Value of i is " + i);
        // Calling the display method
        ifunc.display();
    }
    
}

Output

Value of i is 7

It can be seen if we are not changing the value of local variable i, it can be used with lambda expression and there is no need to declare i as final.

When will it give error

As already pointed out while discussing effectively final "A lambda expression may only use local variable whose value doesn't change". So if you try to change the value of i with in the lambda expression you'll get the error "Local variable i defined in an enclosing scope must be final or effectively final".

Code that gives compiler error

@FunctionalInterface
interface  IFunc{
    void display();
}

public class InnerDemo {
    public static void main(String[] args) {
        int i = 7;
        // lambda expression that implements the display method 
        // of the IFunc functional interface 
        IFunc ifunc = ()-> System.out.println("Value of i is " + i++);
        // Calling the display method
        ifunc.display();
    }   
}

Here I have changed the i to i++ in System.out thus the program will give compiler error "Local variable i defined in an enclosing scope must be final or effectively final".

So it should be clear by now what it means for a variable to be effectively final and why do you get this error "local variable defined in an enclosing scope must be final or effectively final".

Solution to get around this error

Since it is a rule in Java programming language that variable in an enclosing scope can't be change in inner class or lambda expression, so you can't change the value of the variable. That said, there is a get around which I have used and that get around is to use array.

If we take the previous example again by using an int[] array instead of int variable-

@FunctionalInterface
interface  IFunc{
 void display();
}

public class InnerDemo {
 
 public static void main(String[] args) {
        int[] numArr = {7};
        // lambda expression that implements the display method 
        // of the IFunc functional interface 
        IFunc ifunc = ()-> System.out.println("Value of i is " + (numArr[0]+1));
        // Calling the display method
        ifunc.display();
 }
}

Output

Value of i is 8

As you can see it works now and "local variable defined in an enclosing scope must be final or effectively final" error is not thrown anymore.

Works very well with boolean flags and that's where I have used it. Let's see a small example.

public class Test{
 public static void main(String[] args) {
  List applications = Arrays.asList("A", "B");
  List user = Arrays.asList("A");
  Boolean[] arr = {true}; 
  applications.forEach( a -> {
   for (String str : user) {
    if(a.equals(str)) {
     //error resolved: Local variable flag defined in an enclosing scope must be final or effectively final
     arr[0] = false; 
     break;
    }else{
     arr[0] = true;
    }
   }
 
   if(!arr[0]) {
    System.out.println("Here with false");
   }else{
    System.out.println("Here with true");
   }
  });
 }
}

Output

Here with false
Here with true

Recommendations for learning

  1. Java Programming Masterclass Course
  2. Java In-Depth: Become a Complete Java Engineer!
  3. Spring Framework Master Class Course
  4. Complete Python Bootcamp Course
  5. Python for Data Science and Machine Learning

That's all for this topic How to Resolve Local Variable Defined in an Enclosing Scope Must be Final or Effectively Final Error. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Lambda Expressions in Java 8
  2. Lambda expression and variable scope
  3. How to Fix The Target Type of This Expression Must be a Functional Interface Error
  4. Method Reference in Java
  5. Java Lambda Expressions Interview Questions

You may also like-

  1. Multi-Catch Statement in Java Exception Handling
  2. interface static methods in Java 8
  3. Java Stream API Examples
  4. abstract class in Java
  5. final in Java
  6. ThreadLocal class in Java
  7. How and why to synchronize ArrayList in Java
  8. Best practices for exception handling in Java

4 comments:

  1. The post clearly mentions that you are suggesting how to resolve this issue. If we need to change the variable's value, how are we supposed to do that?

    ReplyDelete
    Replies
    1. You can't change the variables value. I think the author meant to explain the reason for getting this error, so that you can understand it and avoid it.

      Now as to why you can't change the value is as follows:

      Local variables in Java have until now been immune to race conditions and visibility problems because they are accessible only to the thread executing the method in which they are declared. But a lambda can be passed from the thread that created it to a different thread, and that immunity would therefore be lost if the lambda, evaluated by the second thread, were given the ability to mutate local variables.

      Refer this link for detailed explanation: http://www.lambdafaq.org/what-are-the-reasons-for-the-restriction-to-effective-immutability/

      Delete
    2. In the comments section of https://netjs.blogspot.com/2015/06/lambda-expression-and-variable-scope.html I have given one way to get round this error, please have a look.

      Delete
  2. The post title says that it resolves the issue, but doesn't. All it does is explain the issue. Change the post title so people don't get confused.

    ReplyDelete