Как в лямбде изменить внешнюю локальную переменную?

Изменить локальную переменную, в ламбде, стандартными методами невозможно, потому, что локальная переменная должны быть effectively final, то есть не должна изменяться после объявления.

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

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

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

void doLater(Runnable r){
  new Thread(() -> {
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {}
  }).start();
}

void main() {
  int i = 0;
  doLater(() -> {
    // Ошибка - к этому моменту переменная может быть 0, 1, или перестать существовать.
    i += 42;
   });
   i++; // Было бы здесь 43 или 1 - неизвестно.
}

void hackO {
  int[] i = {0}; 
  doLater(() -> {
    // Теперь это компилируется, но поведение непредсказуемо – опасно
    i[0] += 42;
  });
  i[0]++;
}
Поделиться уроком

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *