Я прочитал, что эти переменные являются окончательными из-за проблем с параллелизмом.
Неправильно, это не имеет ничего общего с параллелизмом, это все о том, как лямбда-выражения (и анонимные классы) "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 не может этого сделать.
Чтобы сделать его таким , локальная переменная ДОЛЖНА будет эффективно финальным, поэтому тот факт, что (внутренне) переменная вообще не была зафиксирована , не имеет никакого значения для работающего кода.