2

为什么 Elvis elvis 定义必须是最终的才能在 Thread run() 方法中使用?

 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);
   }
 }
4

3 回答 3

3

变量的值elvis被匿名内部类捕获。

仅 Java(当前)按 value捕获变量。编译器要求变量是最终的,这样在新线程中调用该方法时不会混淆实际使用的变量run:如果您elvis在创建新线程之后但在启动它之前更改了 的值,您会怎么做?期望它做什么?

这是 C# 和 Java 中有效使用闭包的方式之间的区别。有关更多详细信息,请参阅我的闭包文章。Java 7 将使闭包更加简洁——我一直没有关注是否有任何方法可以捕获变量本身而不是特定值。

于 2010-03-28T12:11:05.620 回答
2

这与线程无关,与构造匿名类有关。问题是您正在从匿名类中引用局部变量。现在考虑以下几点:

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

在上面的代码片段中,代码应该打印 5 还是应该打印 6?如果 r 持有对当前堆栈帧的引用以解析 c,则可以想象它可以打印 6。也可以想象它可以更早地绑定/捕获 c 的值并打印 5。Java 强制您将 c 设为 final 以使这一点完全清楚,并免除 Java 需要挂在当前堆栈帧上的问题。

于 2010-03-28T12:17:36.203 回答
1

这不是你问题的一部分,但我只是好奇:如果 Elvis.x 是 AtomicInteger,你为什么要重新分配它?这有点错过了 AtomicInteger 的线程安全性。考虑重写:

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);
   }
 }

也因为它有可变的内容,它不应该是一个枚举。

于 2010-03-28T12:29:22.723 回答