106
84

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Java 23新機能まとめ

Last updated at Posted at 2024-10-13

Java 23が2024/9/17にリリースされました。
https://mail.openjdk.org/pipermail/jdk-dev/2024-September/009395.html
Java 23をリリース
The Arrival of Java 23

LTSではなく新たに入った機能も少ないですが、Module Import Declarationsは長大なimport文を削減してくれそうなので期待です。

JDKをインストールせずに言語やライブラリの新機能を試したい場合にはJava Playgroundが便利です。
https://dev.java/playground/

Samplesに新機能のサンプルがあります。
image.png

資料

詳細はこちら
JDK 23 Release Notes
Java SE 23 Platform JSR 398
OpenJDK JDK 23 GA Release

APIドキュメントはこちら
Overview (Java SE 23 & JDK 23)

追加されたAPIまとめはこちら
https://docs.oracle.com/en/java/javase/23/docs/api/new-list.html

APIの差分はこちら。
https://cr.openjdk.org/~iris/se/23/build/latest/java-se--jdk-22-ga--jdk-23%2B37/

ディストリビューション

MacやLinuxでのインストールにはSDKMAN!をお勧めします

Oracle OpenJDK以外に無償で商用利用できるディストリビューションとしては、次のようなものがあります。
Microsoft Buildは非LTSはお休み。

アップデートは10月に23.0.1が、来年1月に23.0.2がリリースされることになります。

JEP

大きめの変更はJEPでまとまっています。
https://openjdk.org/projects/jdk/23/

今回は12個のJEPが取り込まれました。正式採用は3つです。プレビューからの正式化はありません。プレビューは9つで、そのうち2つが新たに入ったものです。
正式採用ではJavadocコメントがMarkdownで書けるようになったJEP 467が便利そう。あと、新しく入ったプレビューの2つ、switchなどでプリミティブが使えるようになるJEP 455とモジュールimportのJEP 476はどちらもコーディングでの影響が大きそうです。
また、Java 22でプレビューに入ったString Templatesは一旦取り下げられています。

455: Primitive Types in Patterns, instanceof, and switch (Preview)
466: Class-File API (Second Preview)
467: Markdown Documentation Comments
469: Vector API (Eighth Incubator)
473: Stream Gatherers (Second Preview)
471: Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal
474: ZGC: Generational Mode by Default
476: Module Import Declarations (Preview)
477: Implicitly Declared Classes and Instance Main Methods (Third Preview)
480: Structured Concurrency (Third Preview)
481: Scoped Values (Third Preview)
482: Flexible Constructor Bodies (Second Preview)

ツール

JavadocコメントでMarkdownが使えるようになりました。
467: Markdown Documentation Comments

467: Markdown Documentation Comments

JavadocコメントをMarkdownで書けるようになります。
その場合、スラッシュ3つでコメントを始めます。

細かいことはJEPの画像を。
Object-hashcode-diff-3.png

言語機能

言語機能の変更としては、_をラムダパラメータやパターンマッチングなどで使えるUnnamed Variables & Patternsが正式機能になっています。また、スーパークラスのコンストラクタ呼び出しの前にステートメントを書けるStatements before superがプレビューとして導入されました。

455: Primitive Types in Patterns, instanceof, and switch (Preview)
476: Module Import Declarations (Preview)
477: Implicitly Declared Classes and Instance Main Methods (Third Preview)
482: Flexible Constructor Bodies (Second Preview)

455: Primitive Types in Patterns, instanceof, and switch (Preview)

instanceof

instanceofでプリミティブを使うとき、値が欠落しないかどうかが判定基準になります。

jshell> int i = 128
i ==> 128

jshell> i instanceof byte
$2 ==> false

jshell> i = 127
i ==> 127

jshell> i instanceof byte
$4 ==> true

doubleでも精度落ちするかどうかが判定されます。

jshell> double d = 1
d ==> 1.0

jshell> d instanceof int
$2 ==> true

jshell> d = 1.2
d ==> 1.2

jshell> d instanceof int
$4 ==> false

ただし、ラッパークラスのオブジェクトの場合はその基本型のときだけtrueになります。

jshell> Long num = 123L
num ==> 123

jshell> num instanceof int
|  エラー:
|  不適合な型: java.lang.Longをintに変換できません:
|  num instanceof int
|  ^-^

jshell> num instanceof long
$7 ==> true

そしてパターンマッチとして使えるわけですね。

jshell> long num = 123
num ==> 123

jshell> var out = new ByteArrayOutputStream()
out ==>

jshell> if (num instanceof byte b) out.write(b)

jshell> out.toByteArray()
$21 ==> byte[1] { 123 }

jshell> num = 1234
num ==> 1234

jshell> if (num instanceof byte b) out.write(b)

jshell> out.toByteArray()
$24 ==> byte[1] { 123 }

switch

このパターンマッチがswitchでも使えるということなんですけど、定数パターンもあるので、switchですべての型が使えるようになったということになります。

jshell> long num = 1234
num ==> 1234

jshell> switch (num) { case int n -> "いんと"; default -> "それ以外";}
$25 ==> "いんと"

jshell> num = 30_0000_0000L
num ==> 3000000000

jshell> switch (num) { case int n -> "いんと"; default -> "それ以外";}
$27 ==> "それ以外"

if式のようなこともできますね。

boolean flag = isFoo();
var s = switch (flag) {
  case true -> "たった";
  case false -> "おりた";
}

476: Module Import Declarations (Preview)

モジュールごとのimportができるようになりました。java.baseをimportしたらだいたいいけるってなりそうです。あと、外部ライブラリのモジュール対応が進みそうです。

次のようにすると、java.baseモジュールに属するjava.utilもjava.ioもimportされることになります。

import module java.base;

JShellでも--enable-previewをつけるとデフォルトでjava.baseがimportされるようになります。
例えば、java.timeはimportされていなかったのでLocalDateをそのまま使うとエラーになります。

>jshell
|  JShellへようこそ -- バージョン23
|  概要については、次を入力してください: /help intro

jshell> LocalDate.now()
|  エラー:
|  シンボルを見つけられません
|    シンボル:   変数 LocalDate
|    場所: クラス
|  LocalDate.now()
|  ^-------^

--enable-previewを付けるとimport不要で使えます。

>jshell --enable-preview
|  JShellへようこそ -- バージョン23
|  概要については、次を入力してください: /help intro

jshell> LocalDate.now()
$1 ==> 2024-10-11

SwingでGUIプログラムをする場合にはjava.desktopモジュールをimportします。

jshell> import module java.desktop;

jshell> var f = new JFrame("hello")
f ==> javax.swing.JFrame[frame0,0,0,0x0,invalid,hidden, ... tPaneCheckingEnabled=true]

jshell> var b = new JButton("OK")
b ==> javax.swing.JButton[,0,0,0x0,invalid,alignmentX=0 ... xt=OK,defaultCapable=true]

ただ、そうするとListjava.util.Listjava.awt.Listでかぶるので、あいまいになってそのままで使えなくなります。

jshell> List a = null;
|  エラー:
|  Listの参照はあいまいです
|    java.awtのクラス java.awt.Listとjava.utilのインタフェース java.util.Listの両方が一致します
|  List a = null;
|  ^--^

この場合にはjava.util.Listなど実際に使うほうをimportするなどが必要です。

jshell> import java.util.List

jshell> List a = null
a ==> null

477: Implicitly Declared Classes and Instance Main Methods (Third Preview)

3rd Previewになりました。java.baseモジュールがimportされることと、java.io.IOが導入されたこと、そしてそのメソッドがimportされてprintlnだけで使えるようになったことがJava 22との違いです。

パブリックスタティックヴォイドメインの呪文から解放されるやつです。
Javaがパブリックスタティックヴォイドメインの呪文から解放される - きしだのHatena

Javaでは単純なハローワールドを書くために次のようなコードが必要でした。

public class Hello {
  public static void main(String[] args) {
    System.out.println("Hello Java!");
  }
}

これが次のように書けるようになります。

void main() {
  println("Hello Java!");
}

プレビュー機能なので、--enable-previewが必要です。javacでは--source 23も必要になります。ソースを直接実行する場合はjavaコマンドでも--source 23が必要です。

>more Hello.java
void main() {
  println("Hello Java!");
}

>java --enable-preview --source 23 Hello.java
ノート: Hello.javaはJava SE 23のプレビュー機能を使用します。
ノート: 詳細は、-Xlint:previewオプションを指定して再コンパイルしてください。
Hello Java!

publicclassstaticなどのキーワードが消え、[]という謎の記号も消えました。
プログラムを勉強するときに、まずやりたいことは処理を書くことです。
クラスは書いた処理をうまく構成するための仕組みなので、処理が書けないうちに勉強してもあまり意味がありません。
publicなどアクセス指定はプログラムが大きくなったときに不適切な要素を使ってしまわないための仕組みなので、入門時のサンプルでは不要です。
staticを説明するにはクラスやインスタンスの理解が必要になりますが、処理が書ける前に勉強するには早すぎます。
配列も変数を知らないうちに勉強できるものでもなく、入門時のサンプルで引数argsを使うことはあまりありません。
その結果「よくわからないしきたり」のまま放置されがち、というか放置せざるを得ない状態で「System.out.pritlnというのは~」という説明をすることになりますが、クラス名とファイル名が違うので動かせなくてハマってそこまでたどりつけなかったりもします。

ということで、初期に学習するべきことに集中できるようにするために、次のように制約が緩和されました。

  • クラスの定義が不要になる
  • mainメソッドはインスタンスメソッドでよくなる
  • mainメソッドの引数を省略できる
  • mainメソッドがpublicじゃなくてもよくなる

「メソッドも不要でいいのでは?」となると思いますが、現状ではステートメントとメソッドを同レベルで書く仕組みがないため、新たにローカルメソッドのような仕組みが必要になり、「初期に学習するべきことに集中できるようにするため」としては影響範囲が大きいので残されています。

このあたりは、次のデザインノートにまとめられています。
https://openjdk.org/projects/amber/design-notes/on-ramp

mainメソッドはインスタンスメソッドでもよくなる

mainメソッドにstaticをつけなくてもよくなります。そして、mainメソッドにstaticをつけなくてもいいということは、そこから呼び出すメソッドなどにもstaticをつけなくていいということになるので、少し大きめのサンプルが書きやすくもなります。

public class Hello {
  public void main(String[] args) {
    foo();
  }
  void foo() {
    System.out.println("Hello");
  }
}

mainメソッドの引数を省略できる / mainメソッドがpublicじゃなくてもよくなる

書かなくてよさそうなものを書かずにすむのですっきりします。
mainメソッドをprivateにすることはできません。protectedは可能です。
「public static void main(String[] args)」を何も見ずに書けるようになったときにJavaに馴染んだ満足感があったので、それがなくなるのは寂しいですが、単なるノスタルジーなのでなくていいと思います。

クラスの定義が不要

クラスを知らなくていいことの他に、クラス名を考えなくていいとかインデントが一段浅くなるとか、中カッコが一組だけになるので間違いが減るとか、いろいろ入門がやりやすくなります。
クラスを省略してmainメソッドにインスタンスメソッドを使うと、new Object(){}で囲まれることになりました。

new Object() {
  void main() {
    println("Hello Java");
  }
}.main();

ただ、クラス定義を省略する場合、ファイル名にstatic.javaなどキーワードを付けるとエラーになります。クラス定義がある場合、javaコマンドで直接実行であれば問題ないです。

>java --enable-preview --source 23 static.java
static.java:3: エラー: 不正なファイル名: static
  void main() {
  ^
エラー1個
エラー: コンパイルが失敗しました

java.io.IOがstatic importされる

java.io.IOというクラスが導入されました。このクラスには、printprintlnreadlnの3つのstaticメソッドが定義されています。
クラス定義を省略して暗黙のクラスを使う場合、これらのメソッドがstaticインポートされるので、System.outを書く必要がなくなります。

ただし、クラスを定義する場合には自動インポートは行われないので、System.outを書くか明示的にインポートが必要です。
先ほどの例でSystem.outを書いていましたが、java.io.IOをstaticインポートすることで省略できます。

import static java.io.IO.*;

public class Hello {
  public void main(String[] args) {
    foo();
  }
  void foo() {
    println("Hello");
  }
}

java.baseがmodule importされる

暗黙のクラスを使う場合、java.baseモジュールもimportされます。
こういうコードはimportなしで書けるようになります。

void main() {
 var data = List.of("test", "data");
 println(data.stream().collect(Collectors.joining(" ")));
}

482: Flexible Constructor Bodies (Second Preview)

States before superだったものが名前が変わって2ndプレビューになりました。

スーパークラスのコンストラクタ呼び出しの前にthisを使わないステートメントが書けるようになります。

例えばListを2つコンストラクタにとるクラスがあるとします。

class Foo {
  Foo(List l1, List l2) {
  }
}

このコンストラクタを継承するとき、Listをふたつ渡す必要があります。

class Bar extends Foo {
  Bar() {
    super(List.of("abc", "def"), List.of("abc", "def"));
  }
}

同じものを渡しているので共通化したいですが、superの前でステートメントを実行できないので迂回を考える必要があります。

class Bar extends Foo {
  private Bar(List l) {
    super(l, l);
  }
  Bar() {
    this(List.of("abc", "def"));
  }
}

これを、次のように書けるようになります。

class Bar extends Foo {
  Bar() {
    var param = List.of("abc", "def");
    super(param, param);
  }

このように、パラメータの検証や構築、共有がある場合に、自然なコードで書けるようになります。
次のようなフィールドのアクセスをsuperの前に行うとエラーになります。

class Bar extends Foo {
  List<String> param;
  Bar() {
    param = List.of("abc", "def");
    super(param, param);
  }
}
test.java:11: エラー: スーパータイプのコンストラクタの呼出し前はparamを参照できません
    param = List.of("abc", "def");
    ^

API

APIの変更としては6つのJEPがありますが新しく入ったのはsun.misc.UnsafeでのメモリアクセスAPIが非推奨になったものだけです。JEPになっていない変更が少しあります。

466: Class-File API (Second Preview)
469: Vector API (Eighth Incubator)
473: Stream Gatherers (Second Preview)
471: Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal
480: Structured Concurrency (Third Preview)
481: Scoped Values (Third Preview)

小さいもの

JEPになっていないAPI変更で、動きがわかりやすいものを挙げます。

NumberFormat.isStrict/setStrict

NumberFormatのparseでは数値と関係ない文字が続いていてもパースしてくれていましたが、setStrict(true)すると例外になります。

jshell> var f = NumberFormat.getInstance()
f ==> DecimalFormat [locale: "日本語 (日本)", pattern: "#,##0.###"]

jshell> f.isStrict()
$9 ==> false

jshell> f.parse("1,234円")
$10 ==> 1234

jshell> f.setStrict(true)

jshell> f.parse("1,234円")
|  例外java.text.ParseException: Unparseable number: "1,234円"
|        at NumberFormat.parse (NumberFormat.java:464)
|        at (#12:1)

Instant.until(Instant)

Instantに、他のInstantとの差をDurationで返すメソッドが入りました。
Duration(startInstant, endInstant)と同じです。

jshell> var i = Instant.now()
i ==> 2024-10-08T21:08:15.820407600Z

jshell> i.until(Instant.now())
$19 ==> PT24.3460241S

jshell> i.until(Instant.now())
$20 ==> PT30.088317S

466: Class-File API (Second Preview)

Javaクラスファイルを解析、変更、生成するためのAPIです。2ndプレビューになりました。
ASMやJavassistなど、同様の機能を実装するライブラリがありますが、Javaではjavacという標準のクラスファイル生成ツールがあります。また、内部的にASMを使っている部分もあります。
ただ、ASMのように外部ライブラリを使うと、こういった外部ライブラリの新機能対応は新バージョンがリリースされるまで正式化しません。そうすると、Javaの新機能をJava内部で使いたいとき、そのASMは非公式であるかひとつ前のバージョン対応であるかということになってしまいます。
そこで、Java標準としてクラスファイル操作のAPIを提供するということになりました。

469: Vector API (Eighth Incubator)

AVX命令のような、複数のデータに対する計算を同時に行う命令をJavaから利用できるようになります。
使うためには実行時やコンパイル時に--add-modules jdk.incubator.vectorをつける必要があります。

Java 16でインキュベータとして導入されたPanamaプロジェクトの残る片割れですが、Java 22からの変更なく8th Incubatorになりました。Project Valhallaのvalue classを使いたいようで、関連JEPがpreviewになるまではIncubatorのままということです。

MemorySegmentにはバイト配列に対してVectorアクセスができていましたが、プリミティブ配列にVectorアクセスできるようになったようです。

基本的な使い方は次のようになります。

import jdk.incubator.vector.*;
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;

void vectorComputation(float[] a, float[] b, float[] c) {

    for (int i = 0; i < a.length; i += SPECIES.length()) { // SPECIES.length() = 256bit / 32bit -> 8
        VectorMask<Float> m = SPECIES.indexInRange(i, a.length); // 端数がマスクされる
                                                                 // a.lengthが11でiが8のとき最初の3つしか要素がないので [TTT.....]
		// FloatVector va, vb, vc;
        FloatVector va = FloatVector.fromArray(SPECIES, a, i, m);
        FloatVector vb = FloatVector.fromArray(SPECIES, b, i, m);
        FloatVector vc = va.mul(va).
                    add(vb.mul(vb)).
                    neg();
        vc.intoArray(c, i, m);
    }
}

利用できるのは次の6つの型です。それぞれに対応するVector型があって、これが基本になります。

bit幅 Vector
byte 8 ByteVector
short 16 ShortVector
int 32 IntVector
long 64 LongVector
float 32 FloatVector
double 64 DoubleVector

ただ、利用するにはVectorSpeciesが必要です。利用したいVectorにSPECIES_*という定数が用意されているので、それを使います。*は一度に計算するbit数ですね。

jshell> FloatVector.SP
SPECIES_128         SPECIES_256         SPECIES_512      
SPECIES_64          SPECIES_MAX         SPECIES_PREFERRED   

MAXではそのハードウェアで使える最大、PREFERREDは推奨ビット数だけど、同じになるんじゃないのかな。ここでは256bitが推奨されて、floatが8個同時に計算できるようになっていますね。

jshell> FloatVector.SPECIES_PREFERRED
$11 ==> Species[float, 8, S_256_BIT]

ハードウェアで使えるbit数は搭載CPUに依存しますが、普通のIntel/AMDであれば256、XEONとか つよつよCPUなら512かな。M1は128でした。ハードウェアでサポートされないbit数を使おうとするとソフトウェア処理になるので遅くなります。

実際のVectorはfrom*というメソッドで取得します。fromArray、fromByteArray、fromByteBufferが用意されています。インキュベータに入る前はfromValuesがあったのですが、なくなってますね。

Vectorを得られたら、用意されたメソッドで計算します。ひととおりの算術命令はあります。

jshell> va.
abs()                    add(                     addIndex(
bitSize()                blend(                   broadcast(
byteSize()               castShape(               check(
compare(                 compress(                convert(
convertShape(            div(                     elementSize()
elementType()            eq(                      equals(
expand(                  fma(                     getClass()
hashCode()               intoArray(               intoMemorySegment(
lane(                    lanewise(                length()
lt(                      maskAll(                 max(
min(                     mul(                     neg()
notify()                 notifyAll()              pow(
rearrange(               reduceLanes(             reduceLanesToLong(
reinterpretAsBytes()     reinterpretAsDoubles()   reinterpretAsFloats()
reinterpretAsInts()      reinterpretAsLongs()     reinterpretAsShorts()
reinterpretShape(        selectFrom(              shape()
slice(                   species()                sqrt()
sub(                     test(                    toArray()
toDoubleArray()          toIntArray()             toLongArray()
toShuffle()              toString()               unslice(
viewAsFloatingLanes()    viewAsIntegralLanes()    wait(
withLane(  

ところで、こういったメソッド呼び出しの内部でAVX命令などを呼び出すのでは遅くなるんではという気がしますが、実際にはJVM intrinsicsという仕組みでJITコンパイラがこれらのメソッド呼び出しをネイティブ関数呼び出しに置き換えます。

473: Stream Gatherers (Second Preview)

Java 22でプレビューとして導入され、評価期間を確保するためそのまま2ndプレビューになりました。

Streamでは、前の値を参照するような処理や、処理順序を前提とした処理が行えません。そのため、前の値にどんどん手を加えてリストを作ったり、移動平均をとったりといったことができませんでした。
そういった、順番を保証して前の値を踏まえた処理が行えるようにするのが、Gathererです。

デザインノートはこちら
https://cr.openjdk.org/~vklang/Gatherers.html

Collectorと同じで自前のGathererを実装するのは大変そうですが、CollectorsのようにGatherersが用意されています。ここでは、Gatherersだけ紹介します。

Streamが使えそうで使えなかった処理に、Streamが使える場合が増えそうです。

JShellを使っていますが、見やすいように改行しているので、そのとおりに入力はできません。試すときは改行せず一行で入力してください。

windowFixed

要素を指定した個数分まとめてListにします。

jshell> IntStream.range(0, 10).boxed()
                 .gather(Gatherers.windowFixed(3))
                 .toList()
$1 ==> [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

windowSliding

スライディングウィンドウです。指定した要素分を、要素をずらしながらとっていってListをつくります。

jshell> IntStream.range(0, 10).boxed()
                 .gather(Gatherers.windowSliding(3))
                 .toList()
$2 ==> [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5],
        [4, 5, 6], [5, 6, 7], [6, 7, 8], [7, 8, 9]]

mapConcurrent

指定した個数分、並列に処理を行いながらmapします。このとき並列処理にはVirtualThreadが使われます。

jshell> IntStream.range(0, 10).boxed()
                 .gather(Gatherers.mapConcurrent(4, n -> n * 2))
                 .toList()
$3 ==> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

処理結果としてはmapと同じになります。

jshell> IntStream.range(0, 10).boxed()
                 .map(n -> n * 2)
                 .toList()
$4 ==> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

scan

計算を追加していきます。

jshell> IntStream.range(0, 10).boxed()
                 .gather(Gatherers.scan(() -> "*", (s, n) -> s + n))
                 .toList()
$7 ==> [*0, *01, *012, *0123, *01234, *012345,
        *0123456, *01234567, *012345678, *0123456789]

fold

処理順を前提としたreduce処理です。

jshell> IntStream.range(0, 10).boxed()
                 .gather(Gatherers.fold(() -> "*", (s, n) -> s + n))
                 .toList()
$8 ==> [*0123456789]

reduceではBinaryOperatorをとるので、要素と結果の型が同じになる必要があります。foldはBiFunctionをとるので、値と結果の型が同じである必要はありません。

jshell> IntStream.range(0, 10).boxed().reduce(0, (a, b) -> a + b)
$9 ==> 45

Collectors.reducingと違うのは、parallel streamでも処理順が保証されることです。

jshell> IntStream.range(0, 10).parallel().boxed()
                 .collect(Collectors.reducing(
                         "*", n -> "" + n, (s, t) -> s + t))
$12 ==> "*0*1*2*3*4*5*6*7*8*9"

あと、終端処理が必要になりますね。

jshell> IntStream.range(0, 10).parallel().boxed().gather(Gatherers.fold(() -> "*", (s, n) -> s + n)).findAny().get()
$14 ==> "*0123456789"

471: Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal

Foreign memory APIが正式化されて代替手段が整ったことで、sun.misc.UnsafeでのメモリアクセスAPIが非推奨になりました。
次のLTSであるJava 25までに実行時警告が出るようになり、Java 26以降で例外が発生するようになり、その後で削除されるという流れが予定されています。

480: Structured Concurrency (Third Preview)

Java 19でIncubatorとして含まれていましたが、Java 21、22から変更なく3rd Previewになりました。

並列処理では、複数の処理を実行するときに、両方が終われば正常終了とか、どちらか片方が終われば終了だとか、どちらか一方でも例外が発生したら終了だとか、同時に行う処理で連動することがあります。
しかし、これを既存のjoinwaitなどで制御しようとすると、実際にはjoinからwaitへのGo Toを書くようなコードになって、処理が追えなくなります。
そこで導入されるのが構造化並列性といいます。
こんな感じ。詳しくはあとで書きます!(Java 19のときから言ってる・・・)

Response handle() throws ExecutionException, InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Subtask<String>  user  = scope.fork(() -> findUser());
        Subtask<Integer> order = scope.fork(() -> fetchOrder());

        scope.join()            // Join both forks
             .throwIfFailed();  // ... and propagate errors

        // Here, both forks have succeeded, so compose their results
        return new Response(user.get(), order.get());
    }
}

SubtaskSupplierを継承しているので、Supplierとして扱うほうがいいかもしれません。

481: Scoped Values (Third Preview)

Java 20でIncubateになりJava 21でプレビュー、そのままJava 22で2ndプレビューになりましたが、少し変更が加わって3rd Previewになりました。

同じスレッド内で値を共有したいときThreadLocalを使いますが、値の変更が可能であったり子スレッドに値が引き継がれたり少し重いので、より限定された仕組みを提供する、ということのようです。
つまり、値を引数でひっぱりまわすのは面倒なのでグローバル変数的にstaticフィールドを使いたい程度のモチベーションで値を共有化するときに、スレッドセーフのためのThreadLocalは重すぎる、という感じですね。
たとえば次のような処理があります。

void start() {
  proc1("test");
}

void proc1(String str) {
  System.out.println(str);
  proc2(str);
}

void proc2(String str) {
  System.out.println(str);
}

これを、全部のメソッドにいちいち引数を設定して値をひきまわるのは面倒なのでフィールドを使おう、という場合。

String str;

void start() {
  str = "test";
  proc1();
}

void proc1() {
  System.out.println(str);
  proc2();
}

void proc2() {
  System.out.println(str);
}

これは複数スレッドから呼び出されると正しく動かないことがあります。
スレッドセーフにするためにThreadLocalを使っていました。

final ThreadLocal<String> VAR = new ThreadLocal<>();

void start() {
  VAR.set("test");
  proc1();
}

void proc1() {
  System.out.println(VAR.get());
  proc2();
}

void proc2() {
  System.out.println(VAR.get());
}

しかし、引数を書いて値を持ちまわっていくのめんどいね、くらいのモチベーションで使うにはThreadLocalは重過ぎるので、軽量な値共有手段としてScopedValueが導入されます。

final ScopedValue<String> VAR = new ScopedValue<>();

void start() {
  ScopedValue.where(VAR, "test")
    .run(() -> proc1());
}

void proc1() {
  System.out.println(VAR.get());
  proc2();
}

void proc2() {
  System.out.println(VAR.get());
}

JVM

JVMの変更として1つJEPが導入されています。

474: ZGC: Generational Mode by Default

474: ZGC: Generational Mode by Default

Java 21でJEP 439として入ったZGCでの世代別GCがデフォルトになりました。
旧来の非世代別アルゴリズムはそのうち削除の方向です。

JDK

サポートプラットフォームなどJDKの変更はありません。

106
84
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
106
84

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?