毎度おなじみ半年ぶりのJavaのアップデートです。
Java 21はLTSのリリースが3年から2年に変わって、はじめてのLTSバージョンです。LTSかどうかはOpenJDK的には関係ないのですが、やっぱりこのバージョンを一区切りとする感じですね。
Java 21のJEPは以下の通り。JEPが15もあり、しかもPreviewもしくはIncubatorではないスタンダードJEPも多いのが、LTSという感じですね。。
- 430: String Templates (Preview)
- 431: Sequenced Collections
- 439: Generational ZGC
- 440: Record Patterns
- 441: Pattern Matching for switch
- 442: Foreign Function & Memory API (Third Preview)
- 443: Unnamed Patterns and Variables (Preview)
- 444: Virtual Threads
- 445: Unnamed Classes and Instance Main Methods (Preview)
- 446: Scoped Values (Preview)
- 448: Vector API (Sixth Incubator)
- 449: Deprecate the Windows 32-bit x86 Port for Removal
- 451: Prepare to Disallow the Dynamic Loading of Agents
- 452: Key Encapsulation Mechanism API
- 453: Structured Concurrency (Preview)
やはり注目はJEP 444: Virtual Threadsでしょうね。すでに、Springなどのフレームワークが対応を発表するなど、Virtual Threadが普及するのも意外に早いかもしれません。とはいうものの、Springなどのフレームワークを使っている場合、ユーザーがVirtual Threadを意識することはないはずです。
Project Amber関連のJEPは5つ。スタンダードJEPはパターンマッチングのJEP 440とJEP 441です。JEP 443などが残っているので、パターンマッチングに関する機能はまだすべてではありません。しかし、switch式でパターンマッチングができるようになったのは大きいと思います。
JEP 430は汎用に使える小さなテンプレートエンジン、JEP 445はmainメソッドの簡略化です。
JEP 431はコレクションに関する機能です。これはAPIなので、java.utilパッケージで説明します。
JEP 439 Generational ZGCはZGCに世代別GCの機能を加えたものです。JEP 442 FFMはThird Previewなので、なかなかスタンダードにならないですが、どうやら次のJava 22でスタンダードになるようです(JEP 454)。FFMと同じProject PanamaのJEP 448 Vector APIもなかなかスタンダードにならないですね。
JEP 446 Scoped ValuesとJEP 453 Structured ConcurrencyはVirtual Threadと同じProject Loomで策定されているAPIです。できれば、これらがまとめてJava 21でスタンダードJEPになればよかったのですが、まぁしかたないです。
JEP 449とJEP 451は機能削減。というか、Windowsの32bit版なんてまだあったのかという感じですねw
JEP 452 Key Encapsulation Mechanism APIはセキュリティ関連のJEPです。
と、軽くJEPを説明したところで、APIの変更について紹介していきましょう。今回はAPIの変更がいっぱいあります。
例によってセキュリティ関連のAPIは省略します。また、java.compilerモジュール、java.desktopモジュールの変更は通常の開発では使用しない変更であるため省略します。
廃止になったAPI
Java 21では2クラス、1メソッドが廃止になりました。いずれも、頻繁に使われるAPIではないので、特に問題はないと思います。
クラス
- java.lang.Compiler
- javax.management.remote.rmi.RMIIIOPServerImpl
CompilerクラスもRMIIIOPServerImplクラスもJava 9からforRemoval=trueになっていたので、とうとう削除という感じです。
メソッド
- java.lang.ThreadGroup.allowThreadSuspension(boolean)
「allowThreadSuspensionメソッドなんてあったの?」という感じですが、実際には何も処理を行わず、単にfalseを返すだけのメソッドになっています。このメソッドはJ2SE 1.2からDeprecatedになっており、逆になぜ今まで残していたのかが不思議なくらいですね。
廃止予定のAPI
Java 21で追加された廃止予定のAPIは、メソッドが2種類です。
メソッド
- javax.management.remote.JMXConnector.getMBeanServerConnection(Subject delgationSubject)
- javax.swing.plaf.synth.SynthLookAndFeel.load(URL url)
getMBeanServerConnectionメソッドはオーバーロードが2種類あり、引数のある方がforRemoval=trueになりました。Subjectを指定することはまずないと思うので、引数なしのgetMBeanServerConnectionメソッドを使用すれば大丈夫です。
SynthLookAndFeelクラスはSwingのLook&Feelの1つですが、Swingが使われることもないでしょうし、ましてやLook&FeelにSynth Look&Feelを使うことはまずないと思うので、問題ないと思います。
追加/変更されたAPI
Java 21で追加されたAPIの半分ぐらいはFFM APIですが、Preview JEPなので省略します。FFM API以外にもPreview APIが含まれていますが、それも省略します。
java.base/java.ioパッケージ
java.ioパッケージではAPIの追加はないのですが、変更がありました。
Consoleクラス
コンソールを扱うためのConsoleクラスですが、Java 21でsealedクラスになりました。
実をいうと、ConsoleクラスはJava 19まではfinalクラスでしたが、Java 20でfinalではなくなりました。これは、コンソールを扱うためにJLineライブラリを扱うためだったようです。JLineはJShellでも使われている、コンソールを扱うためのライブラリです。
しかし、他の用途でConsoleクラスの派生クラスを作成させるためではありません。そこで、sealedクラスにすることで、実質的にfinalクラスと同じになりました。
ちなみに、permitsされているクラスはProxyingConsoleです。
java.base/java.langパッケージ
Virtual Threadの導入によりスレッド関連でAPIの追加が行われています。また、Java 21でサポートされるUnicodeのバージョンは15.0のままなのですが、CharacterクラスにもAPI追加が行われています。
Characterクラス
文字の判別メソッドが6種類追加されました。
- static boolean isEmoji(int codePoint)
- static boolean isEmojiPresentation(int codePoint)
- static boolean isEmojiModifier(int codePoint)
- static boolean isEmojiModifierBase(int codePoint)
- static boolean isEmojiComponent(int codePoint)
- static boolean isExtendedPictographic(int codePoint)
今までは絵文字かどうか調べるには、文字がUnicodeのどの面にあるかなど調べなくてはいけなかったのでめんどうくさかったのですが、これで簡単になりました。
なお、Java 21ではUnicodeのバージョンアップはなく、Unicode 15.0のままなので、UnicodeBlockクラスなどは変更ありません。
Mathクラス/StrictMathクラス
clampメソッドのオーバーロードが4種類追加されました。
- static int clamp(long value, int min, int max)
- static long clamp(long value, long min, long max)
- static double clamp(double value, double min, double max)
- static float clamp(float value, float min, float max)
clampメソッドは、valueをminとmaxの間に固定するためのメソッドです。valueがmaxより大きければmaxを返し、minより小さければminを返します。minとmaxの間であればvalueをそのまま返します。
jshell> Math.clamp(1000L, 100L, 200L)
$1 ==> 200
jshell> Math.clamp(1000L, 2000L, 3000L)
$2 ==> 2000
jshell> Math.clamp(1000L, 100L, 2000L)
$3 ==> 1000
jshell>
Stringクラス
indexOfメソッドのオーバーロードが4種と、スプリットに関するメソッドが追加されました。
- int indexOf(int ch, int beginIndex, int endIndex)
- int indexOf(String str, int beginIndex, int endIndex)
- String[] splitWithDelimiters(String regex, int limit)
indexOfメソッドは引数が1つものと、beginIndexが指定できるものが提供されていましたが、それに加えてendIndexを指定できるようになりました。
splitWithDelimitersメソッドは、文字列を分割する時に、分割に使用した正規表現を含めて分割します。
jshell> var text = "ab:cd:e::fg"
text ==> "ab:cd:e::fg"
jshell> text.split(":", 100)
$2 ==> String[5] { "ab", "cd", "e", "", "fg" }
jshell> text.splitWithDelimiters(":", 100)
$3 ==> String[9] { "ab", ":", "cd", ":", "e", ":", "", ":", "fg" }
jshell>
これが不思議なのが、privateメソッドでsplit(String regex, int limit, boolean withDelimiters)があるということです。splitWithDelimitersメソッドも内部でこのsplitメソッドをコールしているだけです。
なので、このprivateメソッドをpublicにすれば、わざわざ長ったらしい名前のsplitWithDelimitersメソッドを追加する意味がないと思うんですよね。
StringBufferクラス/StringBuilderクラス
StringBufferクラスとStringBuilderクラスは、いずれもAbstractStringBuilderクラスの派生クラスです。AbstractStringBuilderクラスはAppendableインタフェースを実装しているので、StringBuffer/StringBuilderクラスもAppendableインタフェースを実装したクラスになります。
しかし、StringBuffer/StringBuilderクラスのimplements節には記述されていなかったので、Javadocの「すべての実装されたインタフェース」にはAppeendableインタフェースが記載されるものの、implements節の方には記載されていませんでした。そのためなのか、Java 21からimplements節にAppendableインタフェースが追記されました。なお、この変更による動作の変更はありません。
また、repeatメソッドのオーバーロードが2種類追加されました。
- StringBuilder repeat(int codePoint, int count)
- StringBuilder repeat(CharSequence cs, int count)
repeatメソッドは文字や文字列を指定した回数だけ繰りかえします。
jshell> var builder = new StringBuilder()
builder ==>
jshell> builder.repeat("a", 10)
$2 ==> aaaaaaaaaa
jshell> builder.toString()
$3 ==> "aaaaaaaaaa"
jshell>
とはいうものの、StringBuilderクラスを使う機会は減りましたね。今は、リテラル文字列の連結(たとえば、"a"+"b"のような文字列の連結)もStringBuilderクラスを使ってないですし。ましてや、StringBufferクラスはもうDeprecatedにしてもいいのではないかと思うぐらいですね。
Thread.Builderインタフェース
順番的にはThreadクラスの方が先ですが、その内部インタフェースとクラスを先に紹介します。
Thread.BuilderインタフェースはThreadFactoryインスタンスを作成したり、スレッドを作成するためのインタフェースです。もちろん、Virtual Threadの導入により追加されたインタフェースです。
Thread.Builderインタフェースはsealed interfaceで、Thread.Builder.ofPlatformクラス、Thread.Builder.ofVirtualクラスだけが派生クラスとして定義されています。これらの実装クラスは後述します。
Thread.Builderインタフェースで定義されているメソッドは以下のとおり。
- Thread.Builder name(String name)
- Thread.Builder name(String prefix, long start)
- Thread.Builder inheritInheritableThreadLocals(boolean inherit)
- Thread.Builder uncaughtExceptionHandler(Thread.UncaughtExceptionHandler ueh)
- Thread unstarted(Runnable task)
- Thread start(Runnable task)
- ThreadFactory factory()
とはいうものの、このThread.Builderインタフェースを直接使うことは、普通の開発ではほとんどないはずです。通常はSpringなどのフレームワークが使うもので、そのユーザーはスレッドがどのように作成されるかは意識しないで大丈夫なはずです。
Thread.Builder.ofPlatformクラス
Thread.Builderインタフェースの実装クラスで、従来のOSのスレッドに対応したJavaのスレッドを生成します。Virtual Threadに対して、従来のスレッドPlatform Threadと呼ぶようになっています。
Thread.Builderインタフェースで定義された以外のメソッドとして、以下の5種類のメソッドが定義されています。
- Thread.Builder.OfPlatform group(ThreadGroup group)
- Thread.Builder.OfPlatform daemon()
- Thread.Builder.OfPlatform daemon(boolean on)
- Thread.Builder.OfPlatform priority(int priority)
- Thread.Builder.OfPlatform stackSize(long stackSize)
いづれも、従来のThreadクラスで定義されていた同等のコンストラクタもしくはメソッドがあります。
Thread.Builder.ofVirtualクラス
Thread.Builderインタフェースの実装クラスで、Virtual Threadに対応しています。ofVirtualクラスはThread.Builderインタフェースで定義されたメソッド以外のメソッドはありません。
Threadクラス
Virtual Threadに対応したため、いろいろとAPIが追加されたように勘違いするかもしれませんが、実際にはほとんど変化がありません。
- static Thread.Builder.OfPlatform ofPlatform()
- static Thread.Builder.OfVirtual ofVirtual()
- static Thread startVirtualThread(Runnable task)
- boolean isVirtual()
ofPlatformメソッドは前述したThread.Builder.ofPlatformクラスのオブジェクトを生成するメソッドです。同様にofVirtualはThread.Builder.ofVirtualオブジェクトを生成します。
ただし、これらのメソッドを使うことはほとんどないはずです。パラレル処理を行うためのフレームワークやライブラリを作成するのであれば使いますが、それ以外の場合はフレームワークがやるはずです。
というか、今でもnew Thread(() -> {...});のような直接スレッドを生成するようなコードを書いていたら、かなりやばいです。後述するExecutorServiceインタフェースを使うようにしましょう。
isVirtualメソッドだけは使用するかもしれませんが、Virtual Threadと分かったからといって、処理を変える必要はないはずなので、通常は使わないと思います。
java.base/java.lang.constantパッケージ
java.lang.constantパッケージはindyやcondyで使用するために必要なインタフェース/クラスを定義していますが、普通は使わないでしょうね。とりあえず、追加されたものだけ列挙しておきます。
ConstatntDescsクラス
定数が4種類追加されました。
- DirectMethodHandleDesc BSM_CLASS_DATA
- DirectMethodHandleDesc BSM_CLASS_DATA_AT
- String CLASS_INIT_NAME
- String INIT_NAME
- MethodTypeDesc MTD_void
MethodTypeDescインタフェース
ファクトリメソッドが2種類追加されました。
- MethodTypeDesc of(ClassDesc returnDesc)
- MethodTypeDesc of(ClassDesc returnDesc, List<ClassDesc> paramDescs)
ModuleDescインタフェース
モジュール用のデスクプリターが追加されました。定義しているメソッドは以下の3種類です。
- static ModuleDesc of(String name)
- String name()
- boolean equals(Object o)
PackageDescインタフェース
パッケージ用のデスクプリターが追加されました。定義しているメソッドは以下の5種類です。
- static ModuleDesc of(String name)
- static PackageDesc ofInternalName(String name)
- String internalName()
- String name()
- boolean equals(Object o)
java.base/java.lang.invokeパッケージ
indy用のインタフェース/クラスなのでこのパッケージも使わないとは思いますが、メソッドのシグネチャが変更されているので、列挙だけしておきます。
MethodHandles.Lookupクラス
- <T> Class<T> accessClass(Class<T> targetClass)
- <T> Class<T> ensureInitialized(Class<T> targetClass)
いずれも<?>が<T>に変更になっています。
java.base/java.lang.constantパッケージ
これもいつものバージョンアップに伴う定数の追加です。
ClassFileFOrmatVersion列挙型
Java 21用の定数が追加されました。それにしても、今まで定数の説明がThe version recognized by ... だったのが、すべてThe version introduced by ...に変更されたのは何か意味があるのでしょうか???
- ClassFileFormatVersion RELEASE_21
java.base/java.utilパッケージ
java.utilパッケージの変更は多いのですが、JEP 431 Sequenced Collectionsによるものがほとんどです。そこで、JEP 431に関するインタフェースから説明を加えていきます。
JEP 431ではコレクションに順序を取り入れ、先頭と末尾へアクセスするメソッドを定義します。また、順序を逆順にするメソッドも定義しています。
これらのメソッドを定義しているインタフェースは3種類あります。
- interface SequencedCollection<E> extends Collection<E>
- interface SequencedSet<E> extends Set<E>, SequencedCollection<E>
- interface SequencedMap<K,V> extends Map<K,V>
インタフェースの継承関係を表したのが、下図です。
リストにはSequencedCollectionインタフェース、セットにはSequencedCollectionの派生インタフェースであるSequencedSetインタフェースが使われます。マップにはSequencedMapインタフェースです。
SequencedCollectionインタフェース
SequencedCollectionインタフェースはCollectionインタフェースの派生インタフェースで、以下の7種類のメソッドを定義しています。
- SequencedCollection<E> reversed()
- void addFirst(E e)
- void addLast(E e)
- E getFirst()
- E getLast()
- E removeFirst()
- E removeLast()
メソッドの動作はメソッド名から分かると思います。
リストはもともと順序があるので、特に違和感なく使えるはずです。
jshell> var list = List.of(0, 1, 2, 3)
list ==> [0, 1, 2, 3]
jshell> list.reversed()
$2 ==> [3, 2, 1, 0]
jshell> list.getFirst()
$3 ==> 0
jshell> list.getLast()
$4 ==> 3
jshell> list.addFirst(-1)
| 例外java.lang.UnsupportedOperationException
| at ImmutableCollections.uoe (ImmutableCollections.java:142)
| at ImmutableCollections$AbstractImmutableList.add (ImmutableCollections.java:258)
| at List.addFirst (List.java:796)
| at (#5:1)
jshell>
reverseメソッドは逆順にしたリストを返すだけで、自分自身の順序は変更しません。
また、List.ofメソッドで作成されたリストはイミュータブルになるので、addFirst/addLast/removeFirst/removeLastメソッドをコールすると、上記のようにUnsupportedOperationException例外をスローします。
なお、reverse以外のメソッドはdefaultメソッドで実装されています。注意が必要なのは、addFirst/addLastメソッドのdefaultメソッドはUnsupportedOperationException例外をスローしていることです。このため、SequencedCollectionインタフェースの実装クラスの中にはaddFirst/addLastメソッドが使えないものがあります。
セット以外のインタフェースでは、Listインタフェース、Dequeインタフェース、BlockingDequeインタフェースがSequencedCollectionインタフェースの派生インタフェースになります。
なお、Queueインタフェースは両端にアクセスできないため、SequencedCollectionインタフェースの派生インタフェースとならないことに注意が必要です(実際にはQueueインタフェースのコンクリートクラスがSequencedCollectionインタフェースを実装していることも多いです)。
次にセットです。
セットは本来は順序はありませんが、SortedSetインタフェースおよびNavigableSetインタフェースの実装クラスだけは順序が存在します。また、その順序は実装クラスによって異なります。
SequencedSetインタフェースはSequencedCollectionインタフェースの派生インタフェースで、SortedSetインタフェースのスーパーインタフェースになります(NavigableSetインタフェースはSortedSetインタフェースの派生インタフェースです)。SequencedSetインタフェースはメソッドを1つだけ定義しています。
- SequencedSet<E> reversed()
reversedメソッドの返り値の型がSequencedSetインタフェースになっただけですね。
SequencedSetインタフェースの派生インタフェースは前述したSortedSetインタフェースおよびNavigableSetインタフェースです。
これらのインタフェースの実装クラスにはTreeSetクラス、LinkedHashSetクラス、ConcurrentSkipListSetクラスがあります。LinkedHashSetクラスだけはSequencedSetインタフェースを直接実装しています。
なお、TreeSetクラスは前述したaddFirst/addLastメソッドのdefaultメソッドがそのまま使われています。つまり、addFirst/addLastメソッドを使用するとUnsupportedOperationException例外がスローされます。
最後がマップです。
マップもセットと同様に基本的には順序はありませんが、SortedMapインタフェースもしくはNavigableMapインタフェースの実装クラスは順序を持ちます。
SequencedMapインタフェースはSortedMapインタフェースのスーパーインタフェースになります(NavigableMapインタフェースはSortedMapインタフェースの派生インタフェースです)。
SequencedMapインタフェースが定義しているメソッドは以下の通り。
- SequencedMap<K,V> reversed()
- Map.Entry<K,V> firstEntry()
- Map.Entry<K,V> lastEntry()
- Map.Entry<K,V> pollFirstEntry()
- Map.Entry<K,V> pollLastEntry()
- V putFirst(K k, V v)
- V putLast(K k, V v)
- SequencedSet<K> sequencedKeySet()
- SequencedCollection<V> sequencedValues()
- SequencedSet<Map.Entry<K,V>> sequencedEntrySet()
SequencedMapインタフェースでは、アクセスする単位がキーと値がペアになったMap.Entryインタフェースとなります。SortedMapインタフェースがfirstKeyメソッドなどを定義しているのとは対照的です。
firstEntryメソッドとpollFirstEntryメソッドの違いは、firstEntryメソッドが単に最初のエントリーと取得するのに対し、pollFirstEntryメソッドは最初のエントリーを返して、そのエントリーをマップから削除するという点にあります。
lastEntryメソッドとpollLastEntryメソッドの違いも同じです。
jshell> var map = new TreeMap<String, String>(Map.of("a", "Alpha", "b", "Bravo", "c", "Charlie"))
map ==> {a=Alpha, b=Bravo, c=Charlie}
jshell> map.firstEntry()
$3 ==> a=Alpha
jshell> map
map ==> {a=Alpha, b=Bravo, c=Charlie}
jshell> map.pollFirstEntry()
$5 ==> a=Alpha
jshell> map
map ==> {b=Bravo, c=Charlie}
jshell>
SequencedMapインタフェースもreversedメソッド以外はdefaultメソッドで定義されています。SequencedMapインタフェースもSequencedCollectionインタフェースと同様にputFirst/putLastメソッドだけはUnsupportedOperationException例外をスローするようになっています。
SequencedMapインタフェースの実装クラスには、TreeMapクラス、LinkedHashMapクラス、ConcurrentSkipListMapクラスがあります。TreeMapクラスとConcurrentSkipListMapクラスはNavigableMapインタフェースを実装したクラスです。LinkedHashMapクラスだけはSequencedMapインタフェースを直接実装します。
なお、TreeSetクラスと同様にTreeMapメソッドもputFirst/putLastメソッドがdefaultメソッドのままになっています。このため、TreeMapクラスでputFirst/putLastメソッドを使用すると、UnsupportedOperationException例外をスローしてしまうことに注意が必要です。
Collectionsクラス
CollectionsクラスにはJEP 431関連のメソッドが4種類と、その他の1種類のメソッドが追加されました。
- static void shuffle(List<?> list, RandomGenerator rnd)
- static <E> SequencedSet<E> newSequencedSetFromMap(SequencedMap<E,Boolean> map)
- static <T> SequencedCollection<T> unmodifiableSequencedCollection(SequencedCollection<? extends T> c)
- static <T> SequencedSet<T> unmodifiableSequencedSet(SequencedSet<? extends T> s)
- static <K,V> SequencedMap<K,V> unmodifiableSequencedMap(SequencedMap<? extends K,? extends V> m)
shuffleメソッドは、今まで引数がリストだけのオーバーロードと、リストとRandomクラスのオーバーロードが提供されていました。これに加えて、RandomGeneratorインタフェースのオーバーロードが追加されました。
これで、Randomクラス以外のSecureRandomクラスなども乱数として扱うことができるようになっています。
他の4種類のメソッドはSequencedXを生成するためのメソッドです。
unmodifiableSequencedXメソッドはSequencedXオブジェクトを変更不可にするためのメソッドで、今までのunmodifiableXメソッドと使い方は変わりません。
もう1つのnewSequencedSetFromMapメソッドはnewSetFromMapメソッドのSequencedSet版です。セットの実装にはマップが使われていますが、そのマップを指定してセットを作るメソッドです。あくまでも内部のマップを指定するためであって、マップからセットに変換するメソッドではないことに注意が必要です。
このため、引数のマップは空でなくてはいけません。
newSequencedSetFromMapメソッドも同じで、引数には空のSequencedMapオブジェクトを指定します。
Localeクラス
Localeクラスには2種類のメソッドが追加されました。
- static Stream<Locale> availableLocales()
- static String caseFoldLanguageTag(String languageTag)
availableLocalesメソッドは使用できるロケールの一覧をStreamオブジェクトで返すメソッドです。今までは配列が戻されるgetAvailableLocalesメソッドを使用していましたが、Stream APIを使用できるようになりました。
もう一方のcaseFoldLanguageTagメソッドは、IETF言語タグを比較する時などのためにCase Foldingするためのメソッドです。
たとえば、Scriptは先頭文字が大文字の略号が使用されるので、小文字で表記されていると大文字に変換されます。
jshell> var tag = "ja-kana-jp"
tag ==> "ja-kana-jp"
jshell> Locale.caseFoldLanguageTag(tag)
$2 ==> "ja-Kana-JP"
jshell>
variant以降のタグは大文字と小文字を区別しないため、小文字に統一されます。ただし、lvariantが使用されている場合はそれ以降のタグは変換されないままになります。
jshell> var tag1 = "ja-kana-jp-x-Java-Standard-Edition"
tag1 ==> "ja-kana-jp-x-Java-Standard-Edition"
jshell> Locale.caseFoldLanguageTag(tag1)
$2 ==> "ja-Kana-JP-x-java-standard-edition"
jshell> var tag2 = "ja-kana-jp-x-lvariant-Java-Standard-Edition"
tag2 ==> "ja-kana-jp-x-lvariant-Java-Standard-Edition"
jshell> Locale.caseFoldLanguageTag(tag2)
$4 ==> "ja-Kana-JP-x-lvariant-Java-Standard-Edition"
jshell>
java.base/java.util.concurrentパッケージ
java.util.concurrentパッケージの変更もJEP 431 Sequenced Collectionsによるものがほとんどです。これらは省略して、Virtual Threadに関連した変更を紹介します。
Executorsクラス
Virtual Threadに関するメソッドが2種類追加されました。
- static ExecutorService newVirtualThreadPerTaskExecutor()
- static ExecutorService newThreadPerTaskExecutor(ThreadFactory threadFactory)
スレッドを使いまわすスレッドプールには応答速度重視型とスループット重視型の2種類があります。一般的には、計算処理がメインであれば応答速度を重視し、Webシステムであればスループットを重視します。
スループットを重視した場合、タスクごとにスレッドを割り当てるThread per Task(もしくはThread per Request)のスレッドプールが用いられてきました。
しかし、従来のスレッドであればスレッド生成が重い処理であり、メモリも大量に使用してしまいます。そこで、導入されたのがVirtual Threadです。
Virtual Threadは従来のPlatform Thread上で動作しますが、ユーザーからは通常のスレッドと同じように動作します。
newVirtualPerTaskExecutorメソッドは、Virtual Threadに対応したThread per Taskタイプのスレッドプールを生成します。
もう一方のnewThreadPerTaskExecutorメソッドは引数にThreadFactoryオブジェクトを指定します。Virtual Threadに対応したThreadFactoryオブジェクトであればVirtual Threadを使用しますが、それ以外では従来のPlatform Threadになります。
java.base/java.util.regexパッケージ
正規表現を表すPatternクラスにメソッドが追加されました。
Patternクラス
Stringクラスで追加されたメソッドの大本のメソッドが追加されました。
- String[] splitWithDelimiters(CharSequence input, int limit)
StringクラスのsplitWithDelimitersメソッドは内部で、PatternクラスのsplitWithDelimitersメソッドをコールしています。
また、APIの変更ではありませんが、正規表現で絵文字のプロパティが使えるようになりました。追加されたプロパティは以下の6種類です。
- Emoji
- Emoji_Presentation
- Emoji_Modifier
- Emoji_Modifier_Base
- Emoji_Component
- Extended_Pictographic
これらのプロパティは\p{IsX}の形式で使用することができます(Xがプロパティ名)。
jshell> Pattern.compile("\\p{IsEmoji}").matcher("😊").matches()
$1 ==> true
jshell>
java.net.http/java.net.httpパッケージ
java.net.httpモジュールはHTTP Client APIを提供しています。その中のHttpClientクラスに変更がありました。
HttpClientクラス
HttpClientクラスは今まで明示的にクローズすることをしませんでした。これに対し、Java 21ではAutoClosableになり、クローズを行えるようになります。また、これに伴い5種類のメソッドが追加されました。
- void close()
- void shutdown()
- void shutdownNow()
- boolean awaitTermination(Duration duration)
- boolean isTerminated()
HttpClientクラスがAutoClosableインタフェースを実装するようになったため、closeメソッドが追加されています。
shutdownメソッドも通信をクローズしますが、sendメソッドやsendAsyncメソッドが完了するまで待ちます。これに対し、shutdownNowメソッドは完了を待たずにクローズします。
また、awaitTerminationメソッドは指定した時間までは、送受信の完了を待ちます。タイムアウトしてしまった場合はfalseを返します。
closeメソッドは基本的にはshutdownメソッドをコールしますが、完了しない場合awaitTerminationメソッドを1日のタイムアウトでコールします。awaitTerminationメソッドでInterruptedException例外がスローされた場合、shutdownNowメソッドをコールしているようです。
最後のisTerminatedメソッドは送受信が完了しているかどうかを調べるメソッドです。
といいうことで、Java 21のAPIの変更について紹介しました。APIの変更という観点からすると、JEP 431の変更が大きかったですね。とはいうものの、リスト以外で先頭や末尾にアクセスする場面というのは、ほとんどないような気がしますが...
また、JEP 444 Virtual Threadへの対応ですが、こちらはAPIの変更としては少ないです。逆にいうと、従来のPlatform Threadと同じように使えるということでもあります。
それ以外の変更は意外に少ないです。個人的にはHTTP ClientのAutoClosable対応がうれしいところですね。
さて、次のJava 22ではFFM APIが入るのかどうか?それによって、APIの変更度合いも違ってくるはずです。