Передача не конечных объектов в ссылки на методы

Какое объяснение, что s.get() снова возвращает "ОДИН" ?

String x = "one";
Supplier<String> s = x::toUpperCase;
System.out.println("s.get() = " + s.get());
x = "two";
System.out.println("s.get() = " + s.get());

Update:

Сравните это с:

String x = "one";
Supplier<String> s = () -> x.toUpperCase();
System.out.println("s.get() = " + s.get());
x = "two";
System.out.println("s.get() = " + s.get());

Он выдает ошибку компиляции.

Ответ 1

В переменных Java, ссылающихся на объекты, обычно называют references. В приведенном выше коде у вас есть две ссылки: x и s.

Строки неизменяемы и любые сделанные изменения представляют другой объект. После создания вы не можете изменить какое-либо состояние объекта String.

В коде оба x и s инициализируются для ссылки на 2 объекта, а затем x делается для ссылки на другой объект, но s все еще относится к одному объекту. обратите внимание, что :: оценивается немедленно, и результирующий объект задается. x может изменить ссылку на другой объект, не зависящий от y

Использование x = "two" только делает x ссылкой на другой объект.

Ответ 2

String - это класс, который можно использовать, и вы делаете

x = "two"; 

сохранение объекта s "неповрежденным" с предыдущим значением "ONE"

Ответ 3

Передача конечных или эффективных конечных переменных требуется только по лямбда-выражениям (причинам, почему это работает так). С ссылками методов, которые оцениваются по-разному,

15.13.3. Оценка времени выполнения метода.

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

поэтому нет необходимости, чтобы переменная была final.

Собственно, неважно, является ли класс неизменным. Скорее, важно, чтобы левая часть ссылки метода была выражением или нет.

Я хотел бы показать краткий пример, чтобы вы поняли:

class A {
    public static void main(String[] args) {

        Supplier<A> supplier1 = A::new; // (1)
        Supplier<A> supplier2 = new A()::self; // (2) 

        A r1 = supplier1.get(); // (3)
        A r2 = supplier2.get(); // (4)
    }

    private A self() { return this; }

}
  • Создан экземпляр поставщика, результат еще не оценен (ссылка метода с типом).
  • Был рассчитан поставщик и его результат (ссылка метода с выражением new A()).
  • Для каждого вызова supplier1.get() он будет переоценен.
  • Будет возвращен результат с шага 2.

Ответ 4

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

java -jar cfr_0_119.jar LambdaTest --decodelambdas false

/*
 * Decompiled with CFR 0_119.
 */
import java.io.PrintStream;
import java.lang.invoke.LambdaMetafactory;
import java.util.function.Supplier;

public class LambdaTest {
    public static void main(String[] args) {
        String x = "one";
        Supplier<String> s = (Supplier<String>)LambdaMetafactory.metafactory(null, null, null, ()Ljava/lang/Object;, toUpperCase(), ()Ljava/lang/String;)((String)x);
        System.out.println("s.get() = " + s.get());
        x = "two";
        System.out.println("s.get() = " + s.get());
    }
}

Итак, ссылка на метод получает копию первого экземпляра x, поэтому он дважды выводит "ONE", а статическая лямбда не создается, а только вызов toUpper

Я также запускал второй пример, который создает лямбда (я пропустил часть, которая не компилирует -

java -jar cfr_0_119.jar LambdaTest --decodelambdas false
/*
 * Decompiled with CFR 0_119.
 */
import java.io.PrintStream;
import java.lang.invoke.LambdaMetafactory;
import java.util.function.Supplier;

public class LambdaTest {
    public static void main(String[] args) {
        String y = "one";
        Supplier<String> sy = (Supplier<String>)LambdaMetafactory.metafactory(null, null, null, ()Ljava/lang/Object;, lambda$0(java.lang.String ), ()Ljava/lang/String;)((String)y);
        System.out.println("sy.get() = " + sy.get());
    }

    private static /* synthetic */ String lambda$0(String string) {
        return string.toUpperCase();
    }
}

Ответ 5

Строки неизменяемы:

String x = "one";
Supplier<String> s = x::toUpperCase;

эквивалентно:

String x = "one";
Supplier<String> s = "one"::toUpperCase;

Ответ 6

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

То, что вы хотите, это Function, то, что принимает аргумент и возвращает результат.

Попробуйте следующее:

String x = "one";
Function<String, String> s = String::toUpperCase;
System.out.println("s.apply(x) = " + s.apply(x));
x = "two";
System.out.println("s.apply(x) = " + s.apply(x));