"Variable example might not have been initialized" in anonymous class
Asked Answered
B

3

5

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
Burdine answered 4/2, 2016 at 15:23 Comment(0)
B
10

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.

Burdine answered 4/2, 2016 at 15:23 Comment(1)
Really nice! Having the decompiled Example$1 class pretty much shows why should other be final. If it wasn't, the Example$1 could potentially be dealing with an out-of-date copy of other, which would be ... strange, to say the least.Arriaga
V
6

You can use "this" to avoid the compilation error:

final Runnable example = new Runnable() {
  @Override
  public void run() {
    System.out.println(this);  // Use "this" on this line
  }
};
Vargueno answered 21/2, 2018 at 23:18 Comment(0)
A
0

To add something to David response

If you have multiple anonymous classes layers and you need to do that.

Store "this" in a field

      new AnonClass1(){
         private AnonClass1 internalRef=this;
    
         public void methodClass1(){
              new AnonClass2(){
                  public void methodClass2(){
                     doSomethingWithClass1(internalRef);
                  }
              }
         }
      }
Andes answered 12/3, 2021 at 20:34 Comment(1)
If you get to the point of nesting anonymous classes and needing to refer to the outer instances, I think it might be worth stopping using anonymous classes. (In any case, you probably also want to make it final).Burdine

© 2022 - 2024 — McMap. All rights reserved.