Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Variable example might not have been initialized" in anonymous class

This self-answered question was inspired by Variable 'snackbar' might not have been initialized. I felt that there was more detail which would be better added separate from that specific question.

Why can the following code not be compiled?

public class Example {
  public static void main(String[] args) {
    final Runnable example = new Runnable() {
      @Override
      public void run() {
        System.out.println(example);  // Error on this line
      }
    };
  }
}

Compilation error:

error: variable example might not have been initialized
like image 306
Andy Turner Avatar asked Dec 06 '22 19:12

Andy Turner


1 Answers

This occurs because of the way that anonymous classes are implemented. You can see this if you make a slight change to the code and then decompile:

    final Runnable other = null;
    final Runnable example = new Runnable() {
      @Override
      public void run() {
        System.out.println(other);
      }
    };

i.e. make the anonymous class refer to a different local variable. This now will compile; we can decompile using javap and see the interface of the anonymous class:

final class Example$1 implements java.lang.Runnable {
  final java.lang.Runnable val$other;
  Example$1(java.lang.Runnable);
  public void run();
}

(Example$1 is the name by which Java internally refers to the anonymous class).

This shows that the compiler has added a constructor to the anonymous class which takes a Runnable parameter; it also has a field called val$other. This name of this field should hint that this field is related to the other local variable.

You can dig into the bytecode further, and see that this parameter is assigned to val$other:

  Example$1(java.lang.Runnable);
    Code:
       0: aload_0
       // This gets the parameter...
       1: aload_1  
       // ...and this assigns it to the field val$other
       2: putfield      #1                  // Field val$other:Ljava/lang/Runnable;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return

So, what this shows is the way that anonymous classes access the variables from their enclosing scope: they are simply passed the value at construction time.

This should hopefully show why the compiler stops you from writing code such as that in the question: it needs to be able to pass the reference to the Runnable to the anonymous class in order to construct it. However, the way that Java evaluates the following code:

final Runnable example = new Runnable() { ... }

is to fully evaluate the right-hand side first, and then assign it to the variable on the left-hand side. However, it needs the value of the variable on the right-hand side in order to pass into the generated constructor of Runnable$1:

final Runnable example = new Example$1(example);

That example hasn't been previously declared is not a problem, since this code is semantically identical to:

final Runnable example;
example = new Example$1(example);

so the error that you get isn't that the variable cannot be resolved - however, example hasn't been assigned a value before it is used as an argument to the constructor, hence the compiler error.


It might be argued that this is simply an implementation detail: it shouldn't matter that the argument has to be passed into the constructor, as there is no way that the run() method can be invoked prior to the assignment.

Actually, that's not true: you can invoke run() before the assignment, as follows:

final Runnable example = new Runnable() {
  Runnable runAndReturn() {
    run();
    return this;
  }

  @Override public void run() {
    System.out.println(example);
  }
}.runAndReturn();

If referring to example inside the anonymous class were allowed, you would be able to write this. Hence, referring to that variable is disallowed.

like image 150
Andy Turner Avatar answered Dec 09 '22 14:12

Andy Turner