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/
資料
詳細はこちら
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つでコメントを始めます。
言語機能
言語機能の変更としては、_
をラムダパラメータやパターンマッチングなどで使える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]
ただ、そうするとList
がjava.util.List
とjava.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!
public
やclass
、static
などのキーワードが消え、[]
という謎の記号も消えました。
プログラムを勉強するときに、まずやりたいことは処理を書くことです。
クラスは書いた処理をうまく構成するための仕組みなので、処理が書けないうちに勉強してもあまり意味がありません。
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
というクラスが導入されました。このクラスには、print
、println
、readln
の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になりました。
並列処理では、複数の処理を実行するときに、両方が終われば正常終了とか、どちらか片方が終われば終了だとか、どちらか一方でも例外が発生したら終了だとか、同時に行う処理で連動することがあります。
しかし、これを既存のjoin
やwait
などで制御しようとすると、実際には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());
}
}
Subtask
はSupplier
を継承しているので、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の変更はありません。