Указатель функции на метод String в Java

Я не понимаю пару вещей с лямбдой.

String s = "Hello World";       
Function<Integer, String> f = s::substring;
s = null;
System.out.println(f.apply(5));

Почему метод f.apply все еще работает, если s = null. В конце концов, объект String должен быть удален GC, потому что нет указателя, указывающего на объект.

Еще одна причина, почему мне здесь не нужен оператор возврата?

Function<Integer, String> f = t -> t + "";

Ответ 1

JLS, раздел 15.13.3 описывает оценку времени выполнения ссылок на методы.

Сроки оценки ссылочного выражения метода более сложны, чем вычисления лямбда-выражений (§15.27.4). Когда ссылочное выражение метода имеет выражение (а не тип), предшествующее разделителю ::, это подвыражение оценивается немедленно. Результат оценки сохраняется до тех пор, пока не будет вызван метод соответствующего типа функционального интерфейса; в этот момент результат используется как целевая ссылка для вызова. Это означает, что выражение, предшествующее разделителю ::, оценивается только тогда, когда программа встречает ссылочное выражение метода и не переоценивается при последующих вызовах типа функционального интерфейса.

(смелый акцент мой)

В основном ссылка на s как и на ссылку метода, сохраняется для последующего выполнения. Здесь строка "Hello World" сохраняется для последующего выполнения ссылки на метод. Из-за этого, даже если вы задали значение s null после объявления ссылки на метод, но перед выполнением Function все равно будет использоваться строка "Hello World".

Установка null не гарантирует, что сборщик мусора соберет его, и он не гарантирует, что он будет собран немедленно.

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

Наконец, тела выражения лямбда имеют 2 формы: выражение и блок операторов с (возможно) оператором return. С

Function<Integer, String> f = t -> t + "";

Это выражение. Эквивалент блока:

Function<Integer, String> f = t -> { return t + "";};

Ответ 2

Позвольте преобразовать эту ссылку метода в лямбду и посмотреть, что произойдет:

String s = "Hello World";
Function<Integer, String> f = i -> s.substring(i); // Doesn't compile!
s = null;
System.out.println(f.apply(5));

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

См.: Является ли метод ссылки кэширования хорошей идеей в Java 8?

Ответ 3

Еще одна причина, почему мне здесь не нужен оператор возврата?

При указании только одного оператора лямбда его значение автоматически возвращается из лямбда.

Function<Integer, String> f = s::substring;

Java работает с передачей по значению. Итак, f является ссылочной копией.