为什么它说"不能在不同方法中定义的内部类中引用非最终变量"?

Kha*_*aza 13 java android final local-variables inner-classes

我有按钮点击监听器,在onCreate()方法中我有一个局部变量

 onCreate() {

 super.onCreate();

 int i = 10;

 Button button = (Button)findViewById(R.id.button);

 button.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            i++;
        }   
    });
Run Code Online (Sandbox Code Playgroud)

为什么java要求让我最终?

bir*_*rdy 26

当onCreate()方法返回时,您的局部变量将从堆栈中清除,因此它们将不再存在.但是匿名类对象new View.OnClickListener()引用了这些变量.因为它是错误的行为所以java不允许你这样做.

在它最终之后它变成一个常数.所以它存储在堆中,可以安全地用在匿名类中.


hrn*_*rnt 13

你的匿名内部类通过获取局部变量的副本来引用它的封闭范围 - 如果你想在匿名内部类中更改int的值,你需要做一些hackery:

final int[] arr = new int[1];
arr[0] = 10;
Button button = (Button)findViewById(R.id.button);

button.setOnClickListener(new View.OnClickListener() {
  arr[0]++;
}
Run Code Online (Sandbox Code Playgroud)


Jon*_*eet 12

因为您在匿名内部类中访问它.你不能这样做.您可以将其设置为final,然后从匿名内部类中读取它,但您无法增加它.

选项:

  • 使它成为外部类的实例变量
  • 使它成为匿名内部类的实例变量
  • 使用包装器 - 例如单元素数组,AtomicInteger或类似的东西

除非我需要i从其他任何地方进入,否则我可能会赞成第二种选择.老实说,我认为第三种选择是一种讨厌的黑客攻击.


Ste*_*ker 5

制作变量final是必要的,因为在幕后,像这样的匿名内部类只是语法糖,它编译到包含方法范围之外的嵌套类.

这意味着内部类无法访问方法内部声明的任何变量,因此编译器会提取另一个技巧 - 并将值复制到类的隐藏构造函数中.为了避免程序员混淆不更新此副本以匹配方法中变量的更改,它必须是最终的,以确保没有此类更改.

由于你的目标是增加一个整数,并且只有引用必须是final(对象本身不必是不可变的),你可以声明a final AtomicInteger i然后按照你希望的回调递增它.