Переменный захват в лямбде - PullRequest
0 голосов
/ 07 апреля 2020

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

Что это за захват переменной?

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

public class Lambda {

  private int instance=0;

  public void m(int i,String s,Integer integer,Employee employee) {

    ActionListener actionListener = (event) -> {
      System.out.println(i);
      System.out.println(s);
      System.out.println(integer);
      System.out.println(employee.getI());
      this.instance++;
      employee.setI(4);
      integer++;//error
      s="fghj";//error
      i++;//error
    };
  }

}

В этом конкретном коде я хочу знать причины, по которым последние три оператора выдают ошибку, и почему мы можем мутировать Employee, поскольку это локальная переменная (Employee - это просто класс с геттерами и сеттерами int i.)

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

Я ценю полный подробный ответ на все факты, которые я упомянул выше.

1 Ответ

5 голосов
/ 07 апреля 2020

Я прочитал, что эти переменные являются окончательными из-за проблем с параллелизмом.

Неправильно, это не имеет ничего общего с параллелизмом, это все о том, как лямбда-выражения (и анонимные классы) "capture" значения переменных.

Я хочу знать причины, по которым последние три оператора выдают ошибку

, поскольку они захваты поэтому они должны быть действительно окончательными .

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

мне нравится знать, почему мы можем изменять this.instance

Поскольку код не захватывает instance, он захватывает this, а this неявно является окончательным.


Причина, по которой

Лямбда - это в основном синтактический c сахар для анонимного класса. Это не совсем верно, но для целей этого объяснения это достаточно верно, и объяснение легче понять для анонимного класса.

Во-первых, в JVM не существует такой вещи, как анонимный класс , На самом деле лямбда-выражения также не существует, но это другая история.

Однако, поскольку Java (язык) имеет анонимные классы, а JVM - нет, компилятор должен подделать его, преобразовав анонимный класс во внутренний класс. (К вашему сведению: внутренние классы также не существуют в JVM, поэтому компилятору тоже приходится это подделывать.)

Давайте сделаем это на примере. Скажем, у нас есть этот код:

// As anonymous class
int i = 0;
Runnable run = new Runnable() {
    @Override
    public void run() {
        System.out.println(i);
    }
}

// As lambda expression:
int i = 0;
Runnable run = () -> System.out.println(i);

Для анонимного класса компилятор сгенерирует такой класс:

final class Anon_1 implements Runnable {
    private final int i;
    Anon_1(int i) {
        this.i = i;
    }
    @Override
    public void run() {
        System.out.println(i);
    }
}

, а затем скомпилирует код в:

int i = 0;
Runnable run = new Anon_1(i);

Вот как работает capture , копируя значение переменной "captured".

Переменная не captured в общем, значение есть, потому что Java является передачей по значению в вызове конструктора.

Теперь вы можете утверждать, что нет никаких причин, по которым i должен быть эффективно финальным. Конечно, локальная переменная i и поле i теперь разделены, но они могут быть изменены отдельно.

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

Для ясности кода это должно быть как если бы локальная переменная i была захвачена , и что i в анонимном классе является таким же как i снаружи, потому что это что определяет язык Java, хотя JVM не может этого сделать.

Чтобы сделать его таким , локальная переменная ДОЛЖНА будет эффективно финальным, поэтому тот факт, что (внутренне) переменная вообще не была зафиксирована , не имеет никакого значения для работающего кода.

...