为啥传递给runnable的变量必须是final的?

Posted

技术标签:

【中文标题】为啥传递给runnable的变量必须是final的?【英文标题】:Why do variables passed to runnable need to be final?为什么传递给runnable的变量必须是final的? 【发布时间】:2012-07-10 14:30:48 【问题描述】:

假设我有一个变量int x = 1,并且我在主线程中声明了一个runnable,并且我想将x 传递给runnable 的run() 方法,那么它必须声明为final。为什么?

final int x = 0;//<----must be final...
private class myRun implements Runnable 

    @Override
    public void run() 
        x++;//
    


【问题讨论】:

因为这就是语言的定义方式。大概是为了防止变量匿名内部类中的所述方法中被修改。 (我确实相信它也简化了实现:只有 values 需要被代理复制到匿名类型中,并且不再需要保留原始变量,这是完全闭包语义所要求的.) 如果不是这种情况,您的变量可能随时被修改而不会发出警告。 【参考方案1】:

因为这是语言规范所说的。 According to Guy Steele,这种选择背后的基本原理是程序员希望方法中的声明 int x = 0 会导致堆栈分配存储,但是如果您可以从方法返回 new myRun()(或者让 myRun坚持超过函数的返回),然后你可以修改它,然后 x 必须被堆分配而不是拥有你期望的语义。

他们本可以这样做,事实上其他语言也这样做了。但是 Java 设计者决定改为要求您将 x 标记为 final,以避免要求实现堆分配看起来像堆栈分配的存储。

(我应该注意:这并不特定于Runnable。它适用于任何匿名内部类。)

【讨论】:

【参考方案2】:

因为如果可以更改,可能会导致很多问题,请考虑以下几点:

public void count()

    int x;

    new Thread(new Runnable()
    
        public void run()
        
            while(x < 100)
            
                x++;
                try
                
                    Thread.sleep(1000);
                catch(Exception e)
            
        
     ).start();

     // do some more code...

     for(x = 0;x < 5;x++)
         for(int y = 0;y < 10;y++)
             System.out.println(myArrayElement[x][y]);
 

这是一个粗略的示例,但您可以看到很多无法解释的错误可能发生在哪里。这就是为什么变量必须是最终的。这是解决上述问题的简单方法:

public void count()

    int x;

    final int w = x;

    new Thread(new Runnable()
    
        public void run()
        
            int z = w;

            while(z < 100)
            
                z++;
                try
                
                    Thread.sleep(1000);
                catch(Exception e)
            
        
     ).start();

     // do some more code...

     for(x = 0;x < 5;x++)
         for(int y = 0;y < 10;y++)
             System.out.println(myArrayElement[x][y]);
  

如果你想要更完整的解释,它有点像同步。 Java 想要阻止您从多个线程中引用一个对象。这里是关于同步的一点点:

http://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

希望这有帮助!

【讨论】:

这不会引起很多问题——许多语言都有完整的闭包:-) Runnable(可能是线程)在此处的主题中的事实与允许修改局部变量是正交的来自匿名内部类型。即使谈论不涉及线程的东西,也将是相同的语言规则。此外,成员变量也会导致相同的线程问题.. 在您的“这是解决上述问题的简单方法:”中,w++ 不会给您编译错误吗?! w 初始化后不能重新赋值。 @mskw 我不敢相信直到现在还没有人说什么,感谢您发现这一点。【参考方案3】:

多线程的最大“问题”,也是使用它的全部原因,是同时发生多件事情。突然之间,您的线程访问的任何不是线程本地的变量的值都可以随时更改。因此,您可能会认为您只是使用此代码打印数字 1-10:

int x = 0;  //supposing that this was allowed to be non-final...
   private class myRun implements Runnable

    @Override
    public void run() 
        for (int i=0; i<10; i++ ) 
            System.Out.Println( x++ );
        
    

但实际上,如果该类中的其他代码更改了 x 的值,您最终可能会打印 230498 - 230508。x 的值可能 事件在循环中间发生变化。如果您不能依赖 x 具有某个值或保留您之前分配给它的值,那么在您的代码中使用它是徒劳的。如果变量的内容可以随时更改,为什么要使用变量?

Java 不只是完全禁止您使用它,而是要求您将其设为final。您可以“承诺”永远不会从另一个线程更改 x 的值,但是为什么不首先将其设置为 final 并让编译器帮助您呢?当然,您只能访问分配给x 的初始值,但是仅仅能够访问变量的初始值总比不能使用它要好,这将有效地切断线程利用来自的数据的能力其他同学。

【讨论】:

以上是关于为啥传递给runnable的变量必须是final的?的主要内容,如果未能解决你的问题,请参考以下文章

eclipse中定义的private变量保存之后都成final是为啥

为啥 Core Data 上下文对象必须通过环境变量传递?

JAVA,为啥final类不能被继承,如果定义为final的类该类里面成员变量不特殊说明则是final类还是非final

为啥我必须将道具传递给构造函数和超级[重复]

为啥 Runnable 中的实例变量在执行时消失了?

为啥在使用单指针传递给函数时必须对二维数组进行类型转换?