Какое объяснение, что 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));