S-JIS[2025-09-23] 変更履歴

StableValue(Java25)

Java25のStableValue(プレビュー版)について。


概要

2025/9/17にリリースされたJava25で、プレビュー版としてStableValueが新設された。

StableValueは、遅延初期化する定数を定義できるインターフェース。
RustのOnceLock相当)

常に使うとは限らない定数で初期化処理が重い場合は、初期化を遅延させたいことがある。
つまり、その定数を持つクラスやインスタンスが生成された時点では初期化を行わず、初めて使うときに一度だけ初期化して、後はその値を使い回す。
そのような場合にStableValueは便利。


Javaの定数は、その定数を持つクラスやインスタンスが生成されるときに初期化される。

private static final String CONSTANT = "value"; // 初期化

public static String getConstant() {
    return CONSTANT;
}
private static final String CONSTANT;
static {
    CONSTANT = "value"; // 初期化
}

public static String getConstant() {
    return CONSTANT;
}

が、初めて使うときに初期化したい場合は、finalを外し、ゲッターメソッド内で初期化を行う。

private static String CONSTANT; // 初期値はnull

public static String getConstant() {
    if (CONSTANT == null) {
        CONSTANT = "value"; // 初期化
    }
    return CONSTANT;
}

ただ、この方法は、スレッドセーフにしようとすると(マルチスレッドで同時に呼ばれても(効率よく)一度のみ初期化するのは)意外と面倒だったりする。
また、finalが付いていた方がJavaによる最適化の手助けになるらしい。

そこでStableValueを使い、以下のように書くことが出来る。

private static final StableValue<String> CONSTANT = StableValue.of();

public static String getConstant() {
    return CONSTANT.orElseSet(() -> "value"); // 初期化
}
private static final Supplier<String> CONSTANT = StableValue.supplier(() -> "value"); // 初期化

public static String getConstant() {
    return CONSTANT.get();
}

StableValueはJava25ではプレビュー版の機能なので、この機能を使いたい場合はコンパイル時にjavacコマンドに--enable-previewを付ける必要があり、
また、実行時にjavaコマンドに--enable-previewを付ける必要がある。
JShellで試す場合もjshellコマンドに--enable-previewを付ける。

> javac --enable-preview --release 25 Example.java
> java --enable-preview Example
> java --enable-preview Example.java

StableValueのメソッド

StableValueのstaticメソッド

メソッド 説明 類似
StableValue<T> of() StableValueオブジェクトを生成する。
この場合、trySet()orElseSet()で値をセットする。
final StableValue<String> OF = StableValue.of();
OF.trySet("value");
 
StableValue<T> of(T contents) 定数を保持するStableValueオブジェクトを生成する。 final StableValue<String> OF_T = StableValue.of("value");  
Supplier<T> supplier(Supplier<T> underlying) 値を返すSupplierを指定してStableValueオブジェクトを生成する。 final Supplier<String> SUPPLIER = StableValue.supplier(()->"value");  
IntFunction<R> intFunction(int size, IntFunction<R> underlying) intに対してR型の値を返すStableValueオブジェクトを生成する。
以下のような式を持つイメージ。
R value = switch(index) {
  case 0 -> 値;
  case 1 -> 値;
  case 2 -> 値;
}
このindexの範囲(最大値)を引数のsizeで指定する。
初めて呼ばれるindexの値に対して、引数underlyingの関数が呼ばれて値が取得される。
final IntFunction<String> INTS = StableValue.intFunction(3, n -> "VALUE" + n);

String getValue(int index) {
  return INTS.apply(index);
}
final StableValue<String> INT0 = StableValue.of("VALUE0");
final StableValue<String> INT1 = StableValue.of("VALUE1");
final StableValue<String> INT2 = StableValue.of("VALUE2");

String getValue(int index) {
  return switch (index) {
    case 0 -> INT0.orElseThrow();
    case 1 -> INT1.orElseThrow();
    case 2 -> INT2.orElseThrow();
    default -> throw new IllegalArgumentException();
  };
}
Function<T, R> function(Set<T> inputs, Function<T, R> underlying) Tに対してR型の値を返すStableValueオブジェクトを生成する。
使用できるTの有効な値は、引数inputsで指定されたもののみ。
(inputsがEnumSetの場合は、内部的に列挙型専用のクラスが使用される)
final Function<String, Integer> FUNC = StableValue.function(Set.of("a", "b", "c"), s -> switch (s) {
  case "a" -> 1;
  case "b" -> 2;
  case "c" -> 3;
  default -> throw new IllegalArgumentException();
});
int n = FUNC.apply("a");
 
List<E> list(int size, IntFunction<E> mapper) List<E>を返すStableValueオブジェクトを生成する。
Listの要素数はsizeで指定する。
Listの各要素の値は、要素のindex毎に初めて取得される際にmapperを使って生成される。

ただし、このListのtoString()に関しては、初期化されていない要素は「.unset」になる。
final List<String> LIST = StableValue.list(3, index -> "VALUE" + index);  
Map<K, V> map(Set<K> keys, Function<K, V> mapper) Map<K, V>を返すStableValueオブジェクトを生成する。
使用できるキーはkeysで指定する。
Mapの各要素の値は、キー毎に初めて取得される際にmapperを使って生成される。
values()やentrySet()でも初期化されるが、keySet()では初期化されない。
なお、ソート順はkeysの順序とは一致しない。(SortedSetを使っても無関係)

また、このMapのtoString()に関しては、初期化されていない要素は「.unset」になる。
final Map<String, Integer> MAP = StableValue.map(Set.of("a", "b", "c"), key ->  switch (key) {
  case "a" -> 1;
  case "b" -> 2;
  case "c" -> 3;
  default -> throw new IllegalArgumentException();
});
 

StableValueのインスタンスメソッド

メソッド 説明
boolean trySet(T contents) 値をセットする。
既にセット済みの場合はfalseを返し、値は変更されない。
OF.trySet("value");
void setOrThrow(T contents) 値をセットする。
既にセット済みの場合はIllegalStateExceptionがスローされる。
OF.setOrThrow("value");
boolean isSet() 値がセットされているかどうかを返す。 boolean b = OF.isSet();
T orElse(T other) 値を取得する。
値がセットされていない場合はotherを返す。(値はセットされない)
String s = OF.orElse("value");

 

T orElseThrow() 値を取得する。
値がセットされていない場合はNoSuchElementExceptionがスローされる。
String s = OF.orElseThrow();
T orElseSet(Supplier<T> supplier) 値を取得する。
値がセットされていない場合はsupplierが返す値がセットされる。
String s = OF.orElseSet(() -> "value");

プレビュー機能へ戻る / Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま