S-JIS[2025-09-23] 変更履歴
JavaのScopedValueクラスについて。
Java25(プレビュー版ではJava21〜24)で導入されたScopedValueクラスは、マルチスレッドで呼ばれるメソッドがサブルーチンのメソッドとオブジェクトを共有できる仕組みを提供する。
ScopedValueクラスは(java.langパッケージなので)暗黙にインポートされており、明示的なimport文無しで使用できる。
サブルーチンを呼び出す際にオブジェクトを共有しようと思ったら、普通はサブルーチンの引数で渡すが、大量にあるといちいち書くのも大変になる。
オブジェクトをフィールドで保持して共有しようと思っても、マルチスレッドで呼ばれる場合はインスタンスフィールドでは保持できないので、従来ならThreadLocalを使うことになる。
しかしThreadLocalは「インスタンスが生きている間は値を保持し続けるような大きなスコープ」で使われるものなので、メソッド呼び出しの間だけ共有するような目的には重い。(特に仮想スレッドで使おうとすると、ThreadLocalの問題点が大きくなってくるらしい)
そこで、軽量なScopedValueが用意された。
ScopedValueクラスの使用例。
ScopedValueオブジェクトは、まずstaticフィールドで定義しておく。
(ScopedValueはjava.langパッケージなので、import無しで使える)
public class ScopedValueExample { private static final ScopedValue<StringBuilder> SB = ScopedValue.newInstance();
void main() { var sb = new StringBuilder(); ScopedValue.where(SB, sb) // whereで、ScopedValueに値をマッピングする .run(() -> { // runで、関数を実行する sub1(); sub2(); }); System.out.println(sb); }
private void sub1() { StringBuilder sb = SB.get(); // マッピングされている値が取得できる sb.append("sub1."); } private void sub2() { StringBuilder sb = SB.get(); // マッピングされている値が取得できる sb.append("sub2."); } }
ScopedValueのwhereメソッドで、「staticフィールドのScopedValueオブジェクト」と値(この例ではStringBuilder)を指定する。
その後のrunメソッド(やcallメソッド)で関数を実行する。
この関数が実行されている間は、ScopedValueオブジェクトから値(この例ではStringBuilder)が取得できる。
getメソッドは(staticフィールドから取得する形式だが)スレッド毎に異なるインスタンスが返る。
なお、ScopedValueのwhereメソッドを呼び出さずにScopedValueオブジェクトからgetしようとすると、NoSuchElementExceptionが発生する。
2つ以上のScopedValueを使いたい場合は、whereメソッドを複数呼び出す。
import java.util.*; public class ScopedValueExample2 { private static final ScopedValue<StringBuilder> SB = ScopedValue.newInstance(); private static final ScopedValue<List<String>> LIST = ScopedValue.newInstance();
void main() { var sb = new StringBuilder(); var list = new ArrayList<String>(); ScopedValue.where(SB, sb).where(LIST, list).run(() -> { sub1(); sub2(); }); System.out.println(sb); System.out.println(list); }
private void sub1() { StringBuilder sb = SB.get(); sb.append("sub1."); List<String> list = LIST.get(); list.add("sub1"); } private void sub2() { StringBuilder sb = SB.get(); sb.append("sub2."); List<String> list = LIST.get(); list.add("sub2"); } }
staticなwhereメソッドでなくSB.where(sb)みたいにして値をバインドすればいいのにと思ったんだけど、そうすると複数の値をバインドできないから、現在のような方法になってるんだろう。
メソッド | 説明 | 例 |
---|---|---|
ScopedValue<T>
newInstance() |
ScopedValueオブジェクトを生成する。 | private static final ScopedValue<StringBuilder>
SB = ScopedValue.newInstance(); |
ScopedValue.Carrier where(ScopedValue<T>
key, T value) |
ScopedValueオブジェクトの値を設定する。 (その後、runメソッド等を呼び出す) |
ScopedValue.where(SB, new StringBuilder()) |
ScopedValue.Carrierは、ScopedValueのwhereメソッドから返されるクラス。
メソッド | 説明 | 例 |
---|---|---|
ScopedValue.Carrier where(ScopedValue<T>
key, T value) |
ScopedValueオブジェクトの値を設定し、新しいScopedValue.Carrierを返す。 | var c1 = ScopedValue.where(SB,
new StringBuilder()); |
void run(Runnable op) |
opを実行する。 | c1.run(() -> { |
R call(Callable<R> op) |
opを実行し、その戻り値を返す。 | String s = c1.call(() -> { |
T get(ScopedValue<T> key) |
ScopedValueにマッピングされている値を取得する。 | StringBuilder sb = c1.get(SB); |
ScopedValueのインスタンスメソッド(get等)は、主にrunメソッド等の関数内で使用する。
メソッド | 説明 | 例 |
---|---|---|
T get() |
マッピングされている値を取得する。 マッピングされていない場合はNoSuchElementExceptionが発生する。 |
StringBuilder sb = SB.get(); |
boolean isBound() |
値がマッピングされているかどうかを返す。 | boolean bound = SB.isBound();
|
T orElse(T
other) |
マッピングされている値を取得する。 マッピングされていない場合はotherを返す。 |
StringBuilder sb = SB.orElse(null); |
T orElseThrow(Supplier<Throwable>
exceptionSupplier) |
マッピングされている値を取得する。 マッピングされていない場合はexceptionSupplierで生成される例外をスローする。 |
StringBuilder sb = SB.orElseThrow(Exception::new); |