- PVSM.RU - https://www.pvsm.ru -
Данная статья является переводом (оригинал [1]) за авторством Sebastian Rabiej [2]
В этой статье мы рассмотрим JEP 502 — Stable Values. Это новая возможность, которая появится в Java 25 — следующем LTS-релизе, намеченном на сентябрь. Фича будет представлена как первая preview-версия, следовательно, все еще может измениться.
StableValue<T> — это контейнер, который хранит единственное значение типа T. После первого присвоения значение становится неизменяемым. Данный подход можно рассматривать как «в итоге становящемся final» - значение (eventually final).
Важно: неизменяемой становится ссылка на объект. Сам объект под этой ссылкой может изменяться.
До Java 25, чтобы добиться неизменности, мы использовали ключевое слово final:
class Controller {
private final EmailSender sender = new EmailSender();
}
У этого подхода есть недостатки.
Если у нас есть final-поле, его нужно инициализировать заранее — через конструктор или как статическое поле. Из-за этого запуск приложения может замедляться: ведь далеко не все поля нужны сразу, верно?
Можно убрать final и сделать ленивую инициализацию:
class PetClinicController {
private EmailSender sender = null;
EmailSender getSender() {
if (sender == null) {
sender = new EmailSender();
}
return sender;
}
void adoptPet(User user, Pet pet) {
// some logic here
getSender().sendEmailTo(user, "You are great person!");
}
}
Так работать будет, и запуск приложения действительно станет быстрее. Но за такой подход придется заплатить:
sender остаётся изменяемым — мы можем присвоить ему другое значение. Придётся полагаться на внешние инструменты/правила код-ревью, чтобы этого не допустить.
Появляется риск NullPointerException, если доступ к полю получить не через геттер.
Геттер хорошо бы сделать потокобезопасным.
Даже если всё учесть, мы мешаем JVM оптимизировать доступ к полю (например, через constant-folding).
Возьмём тот же пример и перенесём его на новую Java 25:
class PetClinicController {
private final StableValue<EmailSender> sender = StableValue.of();
EmailSender getSender() {
return sender.orElseSet(() -> new EmailSender());
}
void adoptPet(User user, Pet pet) {
// some logic here
getSender().sendEmailTo(user, "You are great person!");
}
}
Код очень похож, но StableValue берёт на себя управление null-значением для EmailSender. Благодаря этому невозможно использовать EmailSender, не вызвав прежде метод получения значения.
Значение внутри StableValue гарантированно устанавливается потокобезопасно.
StableValue служит базой для более высокоуровневых функциональных абстракций. Сейчас есть три варианта stable-функций.
Supplier
Функция вычисляется ровно один раз, а результат кешируется и возвращается при следующих обращениях. Можно использовать в нашем контроллере:
class PetClinicController {
private Supplier<EmailSender> sender = StableValue.supplier(() -> new EmailSender());
void adoptPet(User user, Pet pet) {
// some logic here
sender.get().sendEmailTo(user, "You are great person!");
}
}
intFunction
Функция принимает int и вычисляет результат, который затем кешируется для данного значения параметра. Полезно, например, в математических задачах.
private final int SIZE = 3;
private final IntFunction<Integer> INT_FUNCTION = v -> {
// Simulate expensive computation
log("Computing value for: " + v);
return 42;
};
private void runIntFunction() {
var integerIntFunction = StableValue.intFunction(SIZE, INT_FUNCTION);
log(integerIntFunction.apply(1));
log(integerIntFunction.apply(1));
log(integerIntFunction.apply(1));
log(integerIntFunction.apply(2));
}
Результат вызова runIntFunction:
Computing value for: 1
Value: 42
Value: 42
Value: 42
Computing value for: 2
Value: 42
Главная особенность здесь — параметр size: диапазон входов нужно объявить заранее. Если выйти за пределы (например, 3), будет runtime-исключение: Input not allowed: 3.
Function
Более общий вариант intFunction: можно вызывать функцию с чем угодно.
private Set<Color> KEYS = Set.of(Color.GRAY, Color.GOLDEN);
private Function<Color, HowCute> CUTE_FUNCTION = color -> {
System.out.println("Computing cuteness for: " + color);
return switch (color) {
case RED -> HowCute.CUTE;
case GRAY -> HowCute.VERY_CUTE;
case GOLDEN -> HowCute.SUPER_CUTE;
};
};
private void runCuteFunction() {
var cuteFunction = StableValue.function(KEYS, CUTE_FUNCTION);
log(cuteFunction.apply(Color.GOLDEN));
log(cuteFunction.apply(Color.GOLDEN));
log(cuteFunction.apply(Color.GOLDEN));
log(cuteFunction.apply(Color.GRAY));
log(cuteFunction.apply(Color.RED));
}
Результат:
Computing cuteness for: GOLDEN
Value: SUPER_CUTE
Value: SUPER_CUTE
Value: SUPER_CUTE
Computing cuteness for: GRAY
Value: VERY_CUTE
Exception in thread "main" java.lang.IllegalArgumentException: Input not allowed: RED
Как видно, даже если функция в принципе умеет работать с таким входом, но он не указан в заранее разрешённом наборе inputs, вы получите исключение Input not allowed.
Можно использовать неизменяемые коллекции вместе со StableValue. Пока доступны только List и Map.
List<Integer> list = StableValue.list(SIZE, INT_FUNCTION);
Map<Color, HowCute> map = StableValue.map(KEYS, CUTE_FUNCTION);
Из типов коллекций не видно, что внутри используются StableFunction — это удобно для сложных вычислений. Правда, есть ограничение: для списка применима только intFunction.
Stable Values просты и полезны, но стоит помнить и о подводных камнях.
SerializableStableValue не поддерживает Serializable. В самом JEP это не упомянуто, но информацию об этом можно найти тут [3]. Если вы активно используете сериализацию и захотите заменить все final на StableValue, тут могут возникнуть трудности.
Без final
Используя StableValue, не забывайте, что переменной по-прежнему можно присвоить другое значение. Ничто (кроме здравого смысла) не мешает сделать так:
private StableValue<EmailSender> sender = StableValue.of();
void adoptPet(User user, Pet pet) {
sender = null;
// some logic here
sender.orElseSet(...) // Null pointer exception
}
Лучше помечать все поля StableValue как final, хотя это и не обязательно. Ситуация очень напоминает Optional<>.
Возникает вопрос: почему не ввели новый ключевое слово — что-то вроде Lazy, как в других языках? Посмотрим на мотивацию JEP.
Из JEP следует, что цель авторов — ускорить запуск Java-приложений, отделив создание «стабильных» значений от инициализации. При этом обеспечить корректную работу в многопоточной среде и дать JVM возможность применять оптимизации вроде constant-folding.
Они не стремятся:
расширять язык Java новым способом объявления «стабильных» значений;
менять семантику final-полей.
Так что, хотя лично мне хотелось бы видеть «Lazy», я рад появлению инструмента, который помогает оптимизировать код. С нетерпением жду следующих обновлений.
Автор: AnthonyP
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/430520
Ссылки в тексте:
[1] оригинал: https://softwaremill.com/jep-502-stable-values-new-feature-of-java-25-explained/
[2] Sebastian Rabiej: https://softwaremill.com/blog/?author=Sebastian%20Rabiej
[3] тут: https://cr.openjdk.org/~pminborg/stable-values2/api/java.base/java/lang/StableValue.html#performance
[4] Источник: https://habr.com/ru/articles/946682/?utm_source=habrahabr&utm_medium=rss&utm_campaign=946682
Нажмите здесь для печати.