Почему написано, что нельзя ссылаться на неконечную переменную i внутри внутреннего класса, определенного другим методом?

У меня есть прослушиватель нажатия кнопки, а в методе 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++;
        }   
    });

Почему Java просит сделать меня финальным?


person Khawar Raza    schedule 21.10.2011    source источник


Ответы (5)


Когда метод onCreate () возвращается, ваша локальная переменная будет удалена из стека, поэтому они больше не будут существовать. Но объект анонимного класса new View.OnClickListener () ссылается на эти переменные. Конечно, это неправильное поведение, поэтому Java не позволяет вам этого делать.

После того, как он окончательный, он становится постоянным. Таким образом, он хранится в куче и может безопасно использоваться в анонимных классах.

person amukhachov    schedule 21.10.2011
comment
Хорошо. Причина, по которой вы упомянули, что i является локальным, он будет уничтожен после завершения onCreate (), поэтому onClickListener () не сможет получить значение i. Но если i окончательный, то я думаю, он тоже уничтожится после того, как onCreate () уничтожит. Так как же он будет использоваться в onClickListener, даже если он постоянно? - person Khawar Raza; 21.10.2011
comment
Компилятор просто заменит использование i в анонимном классе значениями констант во время компиляции, и у вас не будет проблем с доступом к несуществующей переменной. - person amukhachov; 21.10.2011
comment
Это особый случай для конечных переменных? - person Khawar Raza; 21.10.2011
comment
Нет, офс. Как вы должны знать, переменная в java - это просто ссылка на объект. Если переменная не является окончательной, для ее ссылки может быть установлено значение null. Но когда он окончен, у вас нет возможности его изменить. Таким образом, эта переменная все еще будет существовать после возврата метода. - person amukhachov; 21.10.2011

Ваш анонимный внутренний класс обращается к его охватывающей области, принимая копии локальных переменных - если вы хотите изменить значение int в анонимном внутреннем классе, вам нужно проделать некоторую хакерскую работу:

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

button.setOnClickListener(new View.OnClickListener() {
  arr[0]++;
}
person hrnt    schedule 21.10.2011
comment
Мне этот способ нравится ... Возможно непонятное но хакерское. - person palsch; 09.03.2015
comment
Другой взлом: class Temp { public int i = 10; } final Temp temp = new Temp(), затем используйте temp.i - person KIM Taegyoon; 21.03.2017
comment
Гениально! Спасибо! - person Andrew; 10.10.2017
comment
Я предпочитаю org.apache.commons.lang3.mutable.MutableInt final MutableInt = new MutableInt(10) - person naviram; 14.12.2017

Потому что вы обращаетесь к нему в анонимном внутреннем классе. Вы не можете этого сделать. Вы можете сделать его окончательным, а затем прочитать из анонимного внутреннего класса, но тогда вы не сможете его увеличить.

Опции:

  • Вместо этого сделайте его переменной экземпляра внешнего класса
  • Вместо этого сделайте его переменной экземпляра анонимного внутреннего класса
  • Используйте обертку - например, одноэлементный массив, AtomicInteger или что-то в этом роде

Я бы, вероятно, предпочел второй вариант, если мне не нужно было добираться до i откуда-нибудь еще. Честно говоря, я считаю третий вариант неприятным приемом.

person Jon Skeet    schedule 21.10.2011
comment
В то время как другие только объясняют причины, только вы даете решения (человек, задавший вопрос, может не ожидать мысли :)) .... следовательно, проголосуйте за - person Shirish Herwade; 02.02.2015

Создание переменной final необходимо, потому что под капотом подобные анонимные внутренние классы представляют собой просто синтаксический сахар, который скомпилировать во вложенный класс вне области действия содержащего метода.

Это означает, что ни одна из переменных, объявленных внутри метода, не доступна внутреннему классу, поэтому компилятор использует другой трюк - и копирует значение в скрытом конструкторе вашего класса. Чтобы избежать путаницы у программистов, когда эта копия не обновляется для соответствия изменениям переменной в методе, она должна быть окончательной, чтобы убедиться, что таких изменений нет.

Поскольку ваша цель здесь - иметь увеличивающееся целое число, и только ссылка должна быть окончательной (сам объект не обязательно должен быть неизменным), вы можете объявить final AtomicInteger i, а затем увеличивать его по своему усмотрению из обратного вызова.

person Steven Schlansker    schedule 21.10.2011

Поскольку вам нужно увеличить переменную i, вы не можете сделать ее окончательной. Вместо этого вы можете сделать его членом класса.

person dbm    schedule 21.10.2011