views:

83

answers:

3

Hi,

Why is the Elvis elvis definition has to be final to be used inside the Thread run() method?

 Elvis elvis = Elvis.INSTANCE; // ----> should be final Elvis elvis = Elvis.INSTANCE
 elvis.sing(4);

 Thread t1 = new Thread(
   new Runnable() {
   @Override
   public void run() {
   elvis.sing(6); // --------> elvis has to be final to compile
  }
}
);


 public enum Elvis {
   INSTANCE(2);

   Elvis() {
     this.x = new AtomicInteger(0);
   }

   Elvis(int x){
     this.x = new AtomicInteger(x);
   }

   private AtomicInteger x = new AtomicInteger(0);

   public int getX() { return x.get(); }

   public void setX(int x) {this.x = new AtomicInteger(x);}

   public void sing(int x) {
      this.x = new AtomicInteger(x);
      System.out.println("Elvis singing.." + x);
   }
 }
+3  A: 

The value of the elvis variable is being captured by the anonymous inner class.

Java only (currently) captures variables by value. The compiler requires that the variable is final so that there can be no confusion about what will actually be used when the run method is called in the new thread: if you changed the value of elvis after creating the new thread but before starting it, what would you expect it to do?

This is a difference between the way that closures are effectively available in C# and Java. See my closures article for more details of this. Java 7 will make closures more concise - I haven't been following along to know whether there will be any way of capturing the variable itself rather than a particular value.

Jon Skeet
Capturing the variable requires that the variable be actually stored in a capturable form. Doable for sure, but requires using a substantially different way of compiling the variables in the first place, so I can see the reluctance to just blunder in.
Donal Fellows
@Jon,@Donal The variable elvis itself is enum and it was meant to be a singleton. I would imagine that it can only be initialized once, and is effectively final?
portoalet
@portoalet: No. The *variable* isn't an enum or a singleton. The *variable* is just a normal variable. The enum type itself may only be initialized once, but that's very different from whether a *variable* of that enum type can be assigned different values over its lifetime.
Jon Skeet
Ah ok. That makes sense.
portoalet
+2  A: 

This has nothing to do with threads and everything to do with constructing anonymous classes. The issue is that you are referencing a local variable from within the anonymous class. Now consider the following:

int c = 5;
Runnable r = new Runnable(){ public void run(){ System.out.println(c); } };
c = 6;
r.run();

In the snippet above, should the code print 5 or should it print 6? If r were to hold onto a reference to the current stack frame in order to resolve c, it is conceivable that it could print 6. It is also conceivable that it could bind/capture the value of c earlier and print a 5. Java forces you to make c final so as to make this completely clear and also to absolve Java of the need to hang onto the current stack frame.

Michael Aaron Safyan
@Michael The variable elvis itself is enum and it was meant to be a singleton. I would imagine that it can only be initialized once, and is effectively final?
portoalet
@portoalet, no, that is incorrect. For example, you can assign null to the variable and not just Elvis.INSTANCE, so it is still necessary to make it final.
Michael Aaron Safyan
+1  A: 

this isn't part of your question but i'm just curious: why are you reassigning Elvis.x if it's an AtomicInteger? that kind of misses the point of AtomicInteger's thread-safety. Consider rewriting:

public Elvis {

   final static private Elvis INSTANCE = new Elvis(2);
   static public Elvis getInstance() { return INSTANCE; }

   final private AtomicInteger x; 


   Elvis() { this(0); }

   Elvis(int x){ 
      this.x = new AtomicInteger(x);
   }

   public int getX() { return this.x.get(); }

   public void setX(int x) {this.x.set(x); }

   public void sing(int x) {
      setX(x);
      System.out.println("Elvis singing.." + x);
   }
 }

also since this has mutable content, it shouldn't be an enum.

Jason S
I was playing with Enum Singleton and then I got the error compiling (as in the description above). So Elvis was meant to be a singleton with a mutable content. Oops, yes I should really use AtomicInteger.set method instead of creating a new Atomic Integer. Well spotted.
portoalet