Ответ 1
Я не могу воспроизвести ошибку для вашего последнего случая с компилятором Eclipse.
Однако аргументация для компилятора Oracle, которую я могу себе представить, такова: внутри лямбда значение obj
должно быть зафиксировано во время объявления. То есть, он должен быть инициализирован, когда он объявлен внутри тела лямбда.
Но в этом случае Java должна отображать значение экземпляра Foo
, а не obj
. Затем он может получить доступ к obj
через ссылку на объект (инициализированный) Foo
и вызвать его метод. Вот как компилятор Eclipse компилирует ваш фрагмент кода.
Это указано в спецификации здесь:
Сроки оценки выражения ссылки метода более сложны чем у лямбда-выражений (§15.27.4). Когда ссылка на метод выражение имеет выражение (а не тип), предшествующее:: разделитель, это подвыражение оценивается немедленно. Результат оценка сохраняется до тех пор, пока метод соответствующего функционального тип интерфейса вызывается; в этот момент результат используется как целевая ссылка для вызова. Это означает, что выражение предшествующий разделителю:: оценивается только тогда, когда программа встречает ссылочное выражение метода и не переоценивается на последующие вызовы по типу функционального интерфейса.
Аналогичная ситуация наблюдается и для
Object obj = new Object(); // imagine some local variable
Runnable run = () -> {
obj.toString();
};
Представьте, что obj
является локальной переменной, когда выполняется код выражения lambda, obj
оценивается и создает ссылку. Эта ссылка хранится в поле в созданном экземпляре Runnable
. При run.run()
называется, экземпляр использует опорное значение, хранящееся.
Это не может произойти, если obj
не инициализирован`. Например
Object obj; // imagine some local variable
Runnable run = () -> {
obj.toString(); // error
};
Лямбда не может записать значение obj
, потому что оно еще не имеет значения. Это эффективно эквивалентно
final Object anonymous = obj; // won't work if obj isn't initialized
Runnable run = new AnonymousRunnable(anonymous);
...
class AnonymousRunnable implements Runnable {
public AnonymousRunnable(Object val) {
this.someHiddenRef = val;
}
private final Object someHiddenRef;
public void run() {
someHiddenRef.toString();
}
}
Вот как компилятор Oracle в настоящее время ведет себя для вашего фрагмента.
Однако компилятор Eclipse вместо этого не записывает значение obj
, он фиксирует значение this
(экземпляр Foo
). Это эффективно эквивалентно
final Foo anonymous = Foo.this; // you're in the Foo constructor so this is valid reference to a Foo instance
Runnable run = new AnonymousRunnable(anonymous);
...
class AnonymousRunnable implements Runnable {
public AnonymousRunnable(Foo foo) {
this.someHiddenRef = foo;
}
private final Foo someHiddenFoo;
public void run() {
someHiddenFoo.obj.toString();
}
}
Это нормально, потому что вы предполагаете, что экземпляр Foo
полностью инициализирован к моменту времени run
.