Интерфейс StableValue<T>
- Параметры типа:
T- тип содержимого
public sealed interface StableValue<T>
StableValue — предварительная версия API платформы Java. StableValue<T> обычно создаётся с помощью фабричного метода StableValue.of(). При таком способе создания стабильное значение находится в состоянии не задано, то есть не содержит содержимого. Его содержимое типа T можно задать вызовом trySet(), setOrThrow() или orElseSet(). После задания содержимое больше не может измениться; получить его можно вызовом orElseThrow() , orElse() или orElseSet().
Рассмотрим следующий пример, в котором поле стабильного значения "logger" является поверхностно неизменяемым контейнером содержимого типа Logger и изначально создаётся в состоянии не задано, то есть не содержит содержимого. Далее в примере проверяется состояние поля "logger", и, если оно всё ещё находится в состоянии не задано, содержимое задаётся:
public class Component {
// Creates a new unset stable value with no contents
private final StableValue<Logger> logger = StableValue.of();
private Logger getLogger() {
if (!logger.isSet()) {
logger.trySet(Logger.create(Component.class));
}
return logger.orElseThrow();
}
public void process() {
getLogger().info("Process started");
// ...
}
}
Если getLogger() вызывается из нескольких потоков, могут быть созданы несколько экземпляров Logger. Однако содержимое можно задать не более одного раза: побеждает первый записавший поток.
Чтобы гарантировать, что даже при гонках будет создан только один экземпляр Logger, вместо этого можно использовать метод orElseSet(). В этом случае содержимое вычисляется лениво и атомарно задаётся с помощью поставщика. В приведённом ниже примере поставщик задан в виде лямбда-выражения:
public class Component {
// Creates a new unset stable value with no contents
private final StableValue<Logger> logger = StableValue.of();
private Logger getLogger() {
return logger.orElseSet( () -> Logger.create(Component.class) );
}
public void process() {
getLogger().info("Process started");
// ...
}
}
Метод getLogger() вызывает logger.orElseSet() для получения содержимого стабильного значения. Если стабильное значение находится в состоянии не задано, orElseSet() вычисляет переданный поставщик и задаёт содержимое равным результату; затем результат возвращается вызывающему коду. Иными словами, orElseSet() гарантирует, что содержимое стабильного значения будет задано до возврата из метода.
Кроме того, orElseSet() гарантирует, что из одного или нескольких переданных поставщиков будет вычислен не более чем один, причём этот поставщик будет вычислен только один раз, даже если logger.orElseSet() вызывается одновременно из нескольких потоков. Это свойство крайне важно, поскольку вычисление поставщика может иметь побочные эффекты: например, приведённый выше вызов Logger.create() может привести к подготовке ресурсов хранилища.
Стабильные функции
Стабильные значения служат основой для функциональных абстракций более высокого уровня. Стабильный поставщик — это поставщик, который вычисляет значение, а затем кэширует его в базовом хранилище стабильного значения для последующего использования. Стабильный поставщик создаётся с помощью фабричного метода StableValue.supplier(), которому передаётся базовый Supplier; он вызывается при первом обращении к стабильному поставщику: public class Component {
private final Supplier<Logger> logger =
StableValue.supplier( () -> Logger.getLogger(Component.class) );
public void process() {
logger.get().info("Process started");
// ...
}
}
Component может получить объект logger непосредственно из стабильного поставщика, не прибегая к методу доступа, например getLogger(). Стабильная функция int — это функция, которая принимает параметр int и использует его для вычисления результата, который затем кэшируется в базовом хранилище стабильного значения для этого значения параметра. Стабильная IntFunction создаётся с помощью фабричного метода StableValue.intFunction(). При создании задаётся диапазон входных значений (то есть [0, size)), а также базовый IntFunction, который вызывается не более одного раза для каждого входного значения. По сути, стабильная функция int работает как кэш для базовой IntFunction:
final class PowerOf2Util {
private PowerOf2Util() {}
private static final int SIZE = 6;
private static final IntFunction<Integer> UNDERLYING_POWER_OF_TWO =
v -> 1 << v;
private static final IntFunction<Integer> POWER_OF_TWO =
StableValue.intFunction(SIZE, UNDERLYING_POWER_OF_TWO);
public static int powerOfTwo(int a) {
return POWER_OF_TWO.apply(a);
}
}
int result = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime
PowerOf2Util.powerOfTwo() является частичной функцией, которая допускает только подмножество [0, 5] входного диапазона UNDERLYING_POWER_OF_TWO базовой функции. Стабильная функция — это функция, которая принимает параметр (типа T) и использует его для вычисления результата (типа R), который затем кэшируется в базовом хранилище стабильного значения для этого значения параметра. Стабильная функция создаётся с помощью фабричного метода StableValue.function(). При создании задаётся входной набор Set, а также базовая Function, которая вызывается не более одного раза для каждого входного значения. По сути, стабильная функция работает как кэш для базовой Function:
class Log2Util {
private Log2Util() {}
private static final Set<Integer> KEYS =
Set.of(1, 2, 4, 8, 16, 32);
private static final UnaryOperator<Integer> UNDERLYING_LOG2 =
i -> 31 - Integer.numberOfLeadingZeros(i);
private static final Function<Integer, Integer> LOG2 =
StableValue.function(KEYS, UNDERLYING_LOG2);
public static int log2(int a) {
return LOG2.apply(a);
}
}
int result = Log2Util.log2(16); // May eventually constant fold to 4 at runtime
Log2Util.log2() является частичной функцией, которая допускает только подмножество {1, 2, 4, 8, 16, 32} входного диапазона UNDERLYING_LOG2 базовой функции. Стабильные коллекции
Стабильные значения также можно использовать как базовое хранилище для неизменяемых коллекций. Стабильный список — это неизменяемый список, поддерживаемый массивом стабильных значений. Элементы стабильного списка вычисляются при первом обращении к ним с помощью переданной IntFunction:final class PowerOf2Util {
private PowerOf2Util() {}
private static final int SIZE = 6;
private static final IntFunction<Integer> UNDERLYING_POWER_OF_TWO =
v -> 1 << v;
private static final List<Integer> POWER_OF_TWO =
StableValue.list(SIZE, UNDERLYING_POWER_OF_TWO);
public static int powerOfTwo(int a) {
return POWER_OF_TWO.get(a);
}
}
int result = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime
Аналогично, стабильная карта — это неизменяемая карта, ключи которой известны при создании. Значения стабильной карты вычисляются при первом обращении к ним с помощью переданной Function:
class Log2Util {
private Log2Util() {}
private static final Set<Integer> KEYS =
Set.of(1, 2, 4, 8, 16, 32);
private static final UnaryOperator<Integer> UNDERLYING_LOG2 =
i -> 31 - Integer.numberOfLeadingZeros(i);
private static final Map<Integer, INTEGER> LOG2 =
StableValue.map(CACHED_KEYS, UNDERLYING_LOG2);
public static int log2(int a) {
return LOG2.get(a);
}
}
int result = Log2Util.log2(16); // May eventually constant fold to 4 at runtime
Композиция стабильных значений
Стабильное значение может зависеть от других стабильных значений, образуя граф зависимостей, который можно вычислять лениво, сохраняя при этом высокую производительность доступа к отдельным элементам. В следующем примере лениво создаются единственный экземплярFoo и экземпляр Bar (зависящий от экземпляра Foo); оба экземпляра хранятся в стабильных значениях: public final class DependencyUtil {
private DependencyUtil() {}
public static class Foo {
// ...
}
public static class Bar {
public Bar(Foo foo) {
// ...
}
}
private static final Supplier<Foo> FOO = StableValue.supplier(Foo::new);
private static final Supplier<Bar> BAR = StableValue.supplier(() -> new Bar(FOO.get()));
public static Foo foo() {
return FOO.get();
}
public static Bar bar() {
return BAR.get();
}
}
bar() создаст единственный экземпляр Bar, если он ещё не создан. При его создании зависимый Foo будет предварительно создан, если Foo ещё не существует. Ещё один пример, с более сложным графом зависимостей, — ленивое вычисление последовательности Фибоначчи:
public final class Fibonacci {
private Fibonacci() {}
private static final int MAX_SIZE_INT = 46;
private static final IntFunction<Integer> FIB =
StableValue.intFunction(MAX_SIZE_INT, Fibonacci::fib);
public static int fib(int n) {
return n < 2
? n
: FIB.apply(n - 1) + FIB.apply(n - 2);
}
}
FIB, и Fibonacci::fib рекурсивно вызывают друг друга. Поскольку стабильная функция int FIB кэширует промежуточные результаты, начальная вычислительная сложность снижается с экспоненциальной до линейной по сравнению с традиционным рекурсивным методом вычисления чисел Фибоначчи без кэширования. После вычисления виртуальная машина может сворачивать в константы такие выражения, как Fibonacci.fib(5). Приведённый выше пример с числами Фибоначчи представляет собой ориентированный ациклический граф (то есть не содержит циклических зависимостей и поэтому является деревом зависимостей):
___________fib(5)____________
/ \
____fib(4)____ ____fib(3)____
/ \ / \
fib(3) fib(2) fib(2) fib(1)
/ \ / \ / \
fib(2) fib(1) fib(1) fib(0) fib(1) fib(0)
Потокобезопасность
Гарантируется, что содержимое стабильного значения будет задано не более одного раза. Если конкурирующие потоки пытаются задать стабильное значение, успешно завершается только одно обновление, а остальные блокируются до тех пор, пока стабильное значение не будет задано; после этого остальные обновления обнаруживают, что стабильное значение задано, и оставляют его без изменений.Успешная операция записи в стабильное значение, выполняемая не более одного раза (например, trySet()), происходит до любой успешной операции чтения (например, orElseThrow()). Успешной операцией записи может быть:
trySet(Object), возвращающийtrue;setOrThrow(Object), не выбрасывающий исключение; илиorElseSet(Supplier), успешно выполняющий поставщик.
orElseThrow(), не выбрасывающий исключение;orElse(other), возвращающий значение, отличное отother;orElseSet(Supplier), не вызывающийthrow; илиisSet(), возвращающийtrue
Метод orElseSet(Supplier) гарантирует, что переданный Supplier будет успешно вызван не более одного раза даже при гонке. Вызовы orElseSet(Supplier) образуют полный порядок: ноль или более вызовов, завершившихся исключением, за которыми следует ноль (если содержимое уже было задано) или один успешный вызов. Поскольку стабильные функции и стабильные коллекции построены на тех же принципах, что и orElseSet(), они также являются потокобезопасными и гарантируют не более одного вызова для каждого входного значения.
Производительность
Поскольку содержимое стабильного значения после задания больше не может измениться, реализация JVM может для заданного стабильного значения исключить все последующие чтения этого значения и вместо этого напрямую использовать содержимое, которое она уже наблюдала. Это справедливо, если ссылка на стабильное значение является константой (например, когда само стабильное значение хранится в полеstatic final). Стабильные функции и коллекции построены на основе StableValue. Поэтому для них также могут быть доступны те же оптимизации JVM, что и для StableValue.- Требования к реализации:
- Реализующие классы
StableValueмогут синхронизироваться наthis, поэтому следует избегать прямой или косвенной синхронизации наStableValue. Следовательно, синхронизация наthisможет привести к взаимной блокировке.За исключением самого содержимого
StableValue, параметра orElse(other) и параметра equals(obj), все параметры методов должны быть не равны null, иначе будет выброшено исключениеNullPointerException. - Примечание по реализации:
StableValueв основном предназначено для непубличного поля класса и обычно не предоставляется напрямую через методы доступа и не передаётся в качестве параметра метода.Стабильные функции и коллекции стараются предоставлять операции
Object.toString(), которые при вызове не запускают вычисление внутренних стабильных значений. Стабильные коллекции предоставляют операцииObject.equals(Object), которые при вызове стараются свести к минимуму вычисление внутренних стабильных значений.Поскольку объекты можно задавать с помощью стабильных значений, но нельзя удалять, это может стать причиной непреднамеренных утечек памяти. Содержимое стабильного значения достижимо по сильной ссылке. Следует учитывать, что достижимые стабильные значения будут удерживать заданное содержимое до тех пор, пока не будет собрано само стабильное значение.
StableValueс параметром типаT, являющимся типом массива произвольной размерности, позволит JVM рассматривать как стабильное значение только ссылку на массив, но не его компоненты. Вместо этого можно использовать стабильный список произвольной глубины, который обеспечивает стабильность компонентов. В более общем случае стабильное значение может содержать другие стабильные значения произвольной глубины и при этом обеспечивать транзитивную неизменяемость.Стабильные значения, функции и коллекции не являются
Serializable.- С версии:
- 25
Краткое описание методов
| Модификатор и тип | Метод | Описание |
|---|---|---|
boolean |
equals |
Возвращает true, если this == obj, иначе false. |
static <T, |
function |
Возвращает новую стабильную Function. |
int |
hashCode() |
Возвращает хеш-код идентичности объекта this. |
static <R> IntFunction |
intFunction |
Возвращает новую стабильную IntFunction. |
boolean |
isSet() |
Возвращает true, если содержимое задано, иначе false. |
static <E> List |
list |
Возвращает новый стабильный список с переданным size. |
static <K, |
map |
Возвращает новую стабильную карту с переданным keys. |
static <T> StableValuePREVIEW |
of() |
Возвращает новое незаданное стабильное значение. |
static <T> StableValuePREVIEW |
of |
Возвращает новое предварительно заданное стабильное значение с переданным contents. |
T |
orElse |
Возвращает содержимое, если оно задано; в противном случае возвращает переданное значение other. |
T |
orElseSet |
Возвращает содержимое; если оно не задано, сначала пытается вычислить и задать его с помощью переданного supplier. |
T |
orElseThrow() |
Возвращает содержимое, если оно задано; в противном случае выбрасывает NoSuchElementException. |
void |
setOrThrow |
Задаёт содержимое этого StableValue равным переданному contents или, если оно уже задано, выбрасывает IllegalStateException. |
static <T> Supplier |
supplier |
Возвращает нового стабильного поставщика. |
boolean |
trySet |
Пытается задать содержимое этого StableValue равным переданному contents. |
Подробное описание методов
trySet
boolean trySet(T contents)
contents. Содержимое этого StableValue можно установить только один раз, поэтому этот метод возвращает true только один раз. К моменту возврата этого метода содержимое этого StableValue всегда установлено.
- Параметры:
-
contents— значение, которое нужно установить - Возвращает:
-
true, если содержимое этого StableValue было установлено в предоставленное значениеcontents, иfalseв противном случае - Вызывает:
-
IllegalStateException— если поставщик, вызванный методомorElseSet(Supplier), рекурсивно пытается установить это стабильное значение, прямо или косвенно вызывая данный метод.
orElse
T orElse(T other)
other.- Параметры:
-
other— значение, возвращаемое, если содержимое не установлено - Возвращает:
- содержимое, если оно установлено, а в противном случае — предоставленное значение
other
orElseThrow
T orElseThrow()
NoSuchElementException.- Возвращает:
- содержимое, если оно установлено, а в противном случае выбрасывает
NoSuchElementException - Вызывает:
-
NoSuchElementException— если содержимое не установлено
isSet
boolean isSet()
true, если содержимое установлено, и false в противном случае.- Возвращает:
-
true, если содержимое установлено, иfalseв противном случае
orElseSet
T orElseSet(Supplier<? extends T> supplier)
supplier. Гарантируется, что предоставленный поставщик supplier будет вызван не более одного раза, если его выполнение завершится без исключения. Если этот метод вызван несколько раз с разными поставщиками, будет вызван только один из них при условии, что его выполнение завершится без исключения.
Если поставщик выбрасывает (непроверяемое) исключение, оно повторно выбрасывается, и содержимое не устанавливается. Чаще всего этот метод используется для создания нового объекта, служащего лениво вычисляемым значением или мемоизированным результатом, например:
Value v = stable.orElseSet(Value::new);
При успешном возврате этого метода содержимое всегда установлено.
Предоставленный поставщик supplier будет вызван только один раз, даже если метод вызывается из нескольких потоков, за исключением случая, когда supplier выбрасывает исключение.
- Параметры:
-
supplier— поставщик, используемый для вычисления содержимого, если оно ещё не установлено - Возвращает:
- содержимое; если оно не установлено, сначала предпринимается попытка вычислить и установить его с помощью предоставленного поставщика
supplier - Вызывает:
-
IllegalStateException— если предоставленныйsupplierрекурсивно пытается установить это стабильное значение.
setOrThrow
void setOrThrow(T contents)
contents или, если оно уже установлено, выбрасывает IllegalStateException. К моменту возврата этого метода (или выбрасывания исключения) содержимое всегда установлено.
- Параметры:
-
contents— значение, которое нужно установить - Вызывает:
-
IllegalStateException— если содержимое уже было установлено -
IllegalStateException— если поставщик, вызванный методомorElseSet(Supplier), рекурсивно пытается установить это стабильное значение, прямо или косвенно вызывая данный метод.
equals
hashCode
int hashCode()
this.- Переопределяет:
-
hashCodeв классеObject - Возвращает:
- идентификационный хеш-код объекта
this - См. также:
of
static <T> StableValuePREVIEW<T> of()
Неустановленное стабильное значение не имеет содержимого.
- Параметры типа:
T— тип содержимого- Возвращает:
- новое неустановленное стабильное значение
of
static <T> StableValuePREVIEW<T> of(T contents)
contents.- Параметры типа:
T— тип содержимого- Параметры:
-
contents— значение, которое нужно установить - Возвращает:
- новое предварительно установленное стабильное значение с предоставленным значением
contents
supplier
static <T> Supplier<T> supplier(Supplier<? extends T> underlying)
Возвращаемый поставщик является кэшируемым поставщиком, который сохраняет значение предоставленного поставщика underlying при первом обращении к методу get() возвращаемого поставщика.
Гарантируется, что предоставленный поставщик underlying будет успешно вызван не более одного раза даже в многопоточной среде. Потоки, конкурирующие за вызов метода get() возвращаемого поставщика, пока значение уже вычисляется, будут заблокированы до вычисления значения или выбрасывания исключения вычисляющим потоком. Затем конкурирующие потоки увидят вновь вычисленное значение (если оно есть) и больше никогда не будут выполнять поставщик underlying.
Если предоставленный поставщик underlying выбрасывает исключение, оно повторно выбрасывается исходному вызывающему коду, а содержимое не сохраняется.
Если предоставленный поставщик underlying рекурсивно вызывает возвращаемого поставщика, будет выброшено исключение IllegalStateException.
- Параметры типа:
T— тип результатов, предоставляемых возвращаемым поставщиком- Параметры:
-
underlying— поставщик, используемый для вычисления кэшированного значения - Возвращает:
- нового стабильного поставщика
intFunction
static <R> IntFunction<R> intFunction(int size, IntFunction<? extends R> underlying)
Возвращаемая функция является кэшируемой функцией, которая для каждого допустимого входного значения int сохраняет значения предоставленной функции underlying при первом обращении к методу apply() возвращаемой функции. Если возвращаемая функция вызывается с входным значением, не входящим в диапазон [0, size), будет выброшено исключение IllegalArgumentException.
Гарантируется, что предоставленная функция underlying будет успешно вызвана не более одного раза для каждого допустимого входного значения даже в многопоточной среде. Потоки, конкурирующие за вызов метода apply() возвращаемой функции, пока значение уже вычисляется, будут заблокированы до вычисления значения или выбрасывания исключения вычисляющим потоком.
Если вызов предоставленной функции underlying выбрасывает исключение, оно повторно выбрасывается исходному вызывающему коду, а содержимое не сохраняется.
Если предоставленная функция underlying рекурсивно вызывает возвращаемую функцию с тем же входным значением, будет выброшено исключение IllegalStateException.
- Параметры типа:
R— тип результатов, предоставляемых возвращаемой IntFunction- Параметры:
-
size— верхняя граница диапазона[0, size), определяющего допустимые входные значения -
underlying—IntFunction, используемая для вычисления кэшированных значений - Возвращает:
- новую стабильную IntFunction
- Вызывает:
-
IllegalArgumentException— если предоставленное значениеsizeотрицательно.
function
static <T,R> Function<T,R> function(Set<? extends T> inputs, Function<? super T, ? extends R> underlying)
Возвращаемая функция является кэшируемой функцией, которая для каждого допустимого входного значения из заданного набора inputs сохраняет значения предоставленной функции underlying при первом обращении к методу apply() возвращаемой функции. Если возвращаемая функция вызывается с входным значением, отсутствующим в inputs, будет выброшено исключение IllegalArgumentException.
Гарантируется, что предоставленная функция underlying будет успешно вызвана не более одного раза для каждого допустимого входного значения даже в многопоточной среде. Потоки, конкурирующие за вызов метода apply() возвращаемой функции, пока значение уже вычисляется, будут заблокированы до вычисления значения или выбрасывания исключения вычисляющим потоком.
Если вызов предоставленной функции underlying выбрасывает исключение, оно повторно выбрасывается исходному вызывающему коду, а содержимое не сохраняется.
Если предоставленная функция underlying рекурсивно вызывает возвращаемую функцию с тем же входным значением, будет выброшено исключение IllegalStateException.
- Параметры типа:
T— тип входных значений возвращаемой FunctionR— тип результатов, предоставляемых возвращаемой Function- Параметры:
-
inputs— набор допустимых (ненулевых) входных значений -
underlying—Function, используемая для вычисления кэшированных значений - Возвращает:
- новую стабильную Function
- Вызывает:
-
NullPointerException— если предоставленный наборinputsсодержит элементnull.
list
static <E> List<E> list(int size, IntFunction<? extends E> mapper)
size. Возвращаемый список является немодифицируемым списком с предоставленным size. Элементы списка вычисляются с помощью предоставленной функции mapper при первом обращении к ним (например, через List::get).
Гарантируется, что предоставленная функция mapper будет успешно вызвана не более одного раза для каждого индекса списка даже в многопоточной среде. Потоки, обращающиеся к элементу, который уже вычисляется, будут заблокированы до вычисления элемента или выбрасывания исключения вычисляющим потоком.
Если вызов предоставленной функции mapper выбрасывает исключение, оно повторно выбрасывается исходному вызывающему коду, а значение элемента не сохраняется.
Представления возвращаемого списка subList и List.reversed() также являются стабильными.
Возвращаемый список и его представления subList или List.reversed() реализуют интерфейс RandomAccess.
Возвращаемый список является немодифицируемым и не реализует необязательные операции необязательные операции интерфейса List.
Если предоставленная функция mapper рекурсивно обращается к возвращаемому списку с тем же индексом, будет выброшено исключение IllegalStateException.
- Параметры типа:
E— тип элементов возвращаемого списка- Параметры:
-
size— размер возвращаемого списка -
mapper— функция, вызываемая при первом обращении к элементу (может возвращатьnull) - Возвращает:
- новый стабильный список с предоставленным
size - Вызывает:
-
IllegalArgumentException— если предоставленное значениеsizeотрицательно.
map
static <K,V> Map<K,V> map(Set<K> keys, Function<? super K, ? extends V> mapper)
keys. Возвращаемая карта является немодифицируемой картой, ключи которой известны при создании. Значения карты вычисляются с помощью предоставленного mapper при первом обращении к ним (например, через Map::get).
Гарантируется, что предоставленная функция mapper будет успешно вызвана не более одного раза для каждого ключа даже в многопоточной среде. Потоки, обращающиеся к значению, которое уже вычисляется, будут заблокированы до вычисления элемента или выбрасывания исключения вычисляющим потоком.
Если вызов предоставленной функции mapper выбрасывает исключение, оно повторно выбрасывается исходному вызывающему коду, а значение, связанное с предоставленным ключом, не сохраняется.
Представления возвращаемой карты Map.values() и Map.entrySet() также являются стабильными.
Возвращаемая карта является немодифицируемой и не реализует необязательные операции необязательные операции интерфейса Map.
Если предоставленная функция mapper рекурсивно обращается к возвращаемой карте с тем же ключом, будет выброшено исключение IllegalStateException.
- Параметры типа:
K— тип ключей, хранящихся в возвращаемой картеV— тип отображаемых значений в возвращаемой карте- Параметры:
-
keys— (ненулевые) ключи возвращаемой карты -
mapper— функция, вызываемая при первом обращении к связанному значению (может возвращатьnull) - Возвращает:
- новую стабильную карту с предоставленным
keys - Вызывает:
-
NullPointerException— если предоставленный наборinputsсодержит элементnull.
© 1993, 2025, Oracle and/or its affiliates. All rights reserved.
Documentation extracted from Debian's OpenJDK Development Kit package.
Licensed under the GNU General Public License, version 2, with the Classpath Exception.
Various third party code in OpenJDK is licensed under different licenses (see Debian package).
Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.
https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/StableValue.html
StableValueтолько при включённых предварительных функциях.