Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Cannot refer to a non-final variable inside an inner class defined in a different method" issue [duplicate]

Tags:

java

I can`t use the variable "i" in the method run(). Is there any way to do it ?

public class Main {

    public static void main(String[] args) {

         final int M = 100;
         final int N = 4;
         final int[] array = new int[M];

         for(int b = 0; b < M; b++) array[b] = b;

         for( int i = 0; i < N; i++) {
             new Thread(new Runnable() {
                 public void run() {
                         for(int a = i*(M/N);a < (i+1)*(M/N); a++)
                             System.out.println("Thread "+i+":"+array[a]); 
                         // i -> cannot refer to a non-final variable inside an inner class defined in a different method
                     }
             }).start();
         } 



    }   
}
like image 726
user2976091 Avatar asked Nov 10 '13 12:11

user2976091


3 Answers

You can declare a final variable p which will take the value of i at each iteration.

    for( int i = 0; i < N; i++) {
       final int p = i;
       new Thread(new Runnable() {
             public void run() {
                  for(int a = p*(M/N);a < (p+1)*(M/N); a++)
                  System.out.println("Thread "+p+":"+array[a]);
             }
        }).start();
     } 

Why I need to have a final declaration ?

like image 82
Alexis C. Avatar answered Oct 07 '22 02:10

Alexis C.


That's right. It is a limitation of Java inner classes! An inner class is not allowed to access an instance variable in an enclosing scope unless that variable is declared as final.

So you need to implement that code like this ...

    for (int i = 0; i < N; i++) {
        final int ii = i;
        new Thread(new Runnable() {
             public void run() {
                 for(int a = ii*(M/N);a < (ii+1)*(M/N); a++)
                     System.out.println("Thread "+ii+":"+array[a]);
             }
        }).start();
    }

The reason for this limitation is to avoid the need for Java to implement closures. Since ii is final, the compiler can implement the above by passing a copy of the value of ii to the anonymous inner class when it instantiates it. This is stored in a hidden variable ... so that it can be used after the enclosing method call completes.

Without this restriction, a (hypothetical) Java compiler would need to create a heap object to hold the i variable. (Or at least it would if it couldn't optimize the object away ...) That's what closures are all about ... at the implementation level.


The other point that people sometimes miss is that this particular algorithm would be incorrect if Java allowed you to access the non-final i. Why? Because by the time that the threads actually started, the outer loop would most likely have finished, and the threads would all see i with the value N. That is NOT what you are trying to achieve here.


UPDATE starting with Java 8, the outer instance variable only needs to be effectively final.

like image 24
Stephen C Avatar answered Oct 07 '22 02:10

Stephen C


Try:

for( int i = 0; i < N; i++) {
    final currentIndex = i; // declare final here
    new Thread(new Runnable() {
        public void run() {
            for(int a = currentIndex*(M/N) ; a < (currentIndex+1)*(M/N) ; a++)
                System.out.println("Thread " + currentIndex + ":" + array[a]); 
         }
     }).start();
} 
like image 23
Jean Logeart Avatar answered Oct 07 '22 00:10

Jean Logeart