关于java:为什么它说“不能在不同方法中定义的内部类中引用非最终变量”?

Why it says that “Cannot refer to a non-final variable i inside an inner class defined in a different method”?

本问题已经有最佳答案,请猛点这里访问。

我有button click listener,在onCreate()方法中,我有一个局部变量,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 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++;
        }  
    });

为什么Java要求我做最后的决定?


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

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


匿名内部类通过复制局部变量来引用其封闭范围-如果要更改匿名内部类中int的值,则需要进行一些黑客操作:

1
2
3
4
5
6
7
final int[] arr = new int[1];
arr[0] = 10;
Button button = (Button)findViewById(R.id.button);

button.setOnClickListener(new View.OnClickListener() {
  arr[0]++;
}


因为你在一个匿名的内部类中访问它。你不能那样做。您可以使它成为最终版本,然后从匿名的内部类中读取它,但是您不能增加它。

选项:

  • 将其改为外部类的实例变量
  • 将其改为匿名内部类的实例变量
  • 使用包装器-例如单个元素数组、AtomicInteger或类似的东西

我可能会赞成第二种选择,除非我需要从其他任何地方去i。老实说,我认为第三种选择有点讨厌。


制作变量final是必要的,因为在hood下,类似这样的匿名内部类只是语法上的糖分,编译为包含方法范围之外的嵌套类。

这意味着方法内部声明的所有变量都不能被内部类访问,因此编译器会采用另一种技巧——在类的隐藏构造函数中复制值。为了避免程序员混淆,如果不更新此副本以匹配方法中变量的更改,则必须最终确定是否存在此类更改。

因为这里的目标是要有一个递增的整数,并且只有引用必须是最终的(对象本身不必是不可变的),所以您可以声明一个final AtomicInteger i,然后根据自己的意愿从回调中递增。


因为您需要增加i变量,所以不能使其成为最终变量。你可以改为成为一个类成员。