2023/03/21

JEPでは語れないJava 20

このエントリーをはてなブックマークに追加

毎度おなじみ半年ぶりのJavaのアップデートです。Javaのバージョンもとうとう20台になってしまいました。

OpenJDK的には関係ないのですが、Oracleをはじめとした多くのディストリビューションではJava 21をLTSとするため、Java 20でPreviewやIncubatorのJEPに区切りをつけたいところです。

Java 20のJEPは以下の通り。すべてがPreviewもしくはIncubatorです。

  • 429: Scoped Values (Incubator)
  • 432: Record Patterns (Second Preview)
  • 433: Pattern Matching for switch (Fourth Preview)
  • 434: Foreign Function & Memory API (Second Preview)
  • 436: Virtual Threads (Second Preview)
  • 437: Structured Concurrency (Second Incubator)
  • 438: Vector API (Fifth Incubator)

このままでいくと、Virtual ThreadsやVictor API、Foreign Function & Memory APIはJava 21で正式にリリースということになりそうです。switch式のパターンマッチングやレコード型のパターンマッチングもたぶん正式リリースになると思います。

間に合わなかったのが、JEP 429 Scoped Valuesです。

Scoped ValuesはVirtual Threadsと同じくProject Loomで策定されており、ThreadLocalクラスの代わりに使えるAPIです。これが入らないとすると、Virtual Threads的にはちょっとつらいですね。

 

まぁ、JEPの機能についてはだれか書いてくれると思うので、いつものごとくJEPには触れられていないAPIの変更について紹介していきます。

今回も、java.baseモジュール以外のモジュールの変更は少ないし、普通の開発ではほぼ使われないAPIなので、今回はjava.baseモジュールだけ説明します。

 

廃止になったAPI

Java 19と同じく、Java 20でも廃止になったAPIはありません。

 

廃止予定のAPI

Java 20で追加された廃止予定のAPIは、JMX Remote関連のクラスです。

Java 20で、JMX Remoteのシリアライズ、デシリアライズの仕様が変更になったのですが、それに伴うことのようです。ただし、このクラスがなくなったとしても、JMXの使い方に変更はないはずです。

 

クラス

前述したようにJMX Remote関連のクラスがforRemoval=trueになりました。

  • javax.management.loading.MLet
  • javax.management.loading.MLetContent
  • javax.management.loading.MLetMBean
  • javax.management.loading.PrivateMLet

 

例外

Threadクラス関連の例外がforRemoval=trueになりました。

  • java.lang.ThreadDeath

例外名にExceptionともErrorともついていませんが、ThreadDeath例外はエラーの1つです。

ThreadDeath例外はThread.stopメソッドがコールされた時にスローされる例外です。Thread.stopメソッドがJava 18でforRemoval=trueになったので、合わせてforRemoval=trueになったのだと思います。

 

追加されたAPI

Java 20で追加されたAPIの半分ぐらいはFFM APIですが、ここでは省略します。

 

java.base/java.langパッケージ

毎度のことですが、Java 20でサポートされるUnicodeのバージョンが15になったことに伴い、Character.UnicodeBlockクラスとCharacter.UnicodeScriptクラスの定数が増えています。その他は細かい変更のみです。

 

Character.UnicodeBlockクラス

Unicode 15.0で導入されたブロックが定数として追加されています。

  • ARABIC_EXTENDED_C
  • CJK_UNIFIED_IDEOGRAPHS_EXTENSION_H
  • CYRILLIC_EXTENDED_D
  • DEVANAGARI_EXTENDED_A
  • KAKTOVIK_NUMERALS
  • KAWI
  • NAG_MUNDARI

CJK_UNIFIED_IDEOGRAPHS_EXTENSION_Hは漢字ですが、ちょっと見た限り、日本で使う漢字はないようですね。

 

Character.UnicodeScriptクラス

Unicode 15.0で導入されたスクリプトが定数として追加されています。

  • KAWI
  • NAG_MUNDARI

 

Classクラス

後述しますが、リフレクション関連でアクセス修飾子やモジュール関連のフラグを定義したAccessFlag列挙型がJava 20で導入されました。

ClassクラスではクラスのAccessFlag列挙型のセットを返すメソッドが追加されました。

  • Set<AccessFlag> accessFlags()

どういう値が返るのか、JShellで試してみました。

jshell> String.class.accessFlags()
$1 ==> [PUBLIC, FINAL, SUPER]

jshell> List.class.accessFlags()
$2 ==> [PUBLIC, INTERFACE, ABSTRACT]

jshell> ArrayList.class.accessFlags()
$3 ==> [PUBLIC, SUPER]

jshell>

 

Floatクラス

Javaのfloatは4バイト、倍制度のdoubleは8バイトで表されます。これに対しIEEE 754には2バイトで表される半精度浮動小数点数が定義されています。

その内訳は符号が1ビット、指数部で5ビット、仮数部で10ビットとなります。

この半精度浮動小数点数とfloatの変換メソッドがFloatクラスに追加されました。

  • static float float16ToFloat(short floatBinary16)
  • static short floatToFloat16(float f)

float16ToFloatメソッドが半精度小数点数からfloat、floatToFloat16がfloatから半精度浮動小数点数への変換を行います。

Javaで2バイトで表される型といえばshortです。そこで、shortを使って半精度浮動小数点数を表します。なので、shortの値として見てしまったら、全然わからないですw

 

ここに紹介した以外にも、ModuleクラスやModuleLayer.Controllerクラスにメソッドが追加されていますが、これらはFFM APIに関連したPreview APIなので、ここでは省略します。

 

java.base/java.lang.constantパッケージ

java.lang.constantパッケージはinvokeDynamic関連でエンティティのNominal Descriptionを定義しているパッケージです。普通はあまり使わないと思いますけど、indyを使って動的に最適化したい時ぐらいですね。

 

ClassDescクラス

クラスのNominal Descriptionを表すClassDescクラスでは、ファクトリメソッドが1つ追加されました。

  • static ClassDesc ofInternalName(String name)

これまでのファクトリメソッド、たとえばofメソッドの引数はクラス名をピリオド区切りで指定しました。たとえば、Stringクラスであればjava.lang.Stringです。

これに対しofInternalNameメソッドはクラスの内部表現で表します。Stringクラスであればjava/lang/Stringとなります。

この内部表現に関してはJVM SpecのJVMS 4.2.1に記載がありますので、参考になさってください。

jshell> import java.lang.constant.*

jshell> ClassDesc.of("java.lang.String")
$2 ==> ClassDesc[String]

jshell> ClassDesc.ofInternalName("java/lang/String")
$3 ==> ClassDesc[String]

jshell>

 

java.base/java.lang.moduleパッケージ

java.lang.moduleパッケージでも、Classクラスと同様に各クラスにaccessFlagsメソッドが追加されています。

 

ModuleDescriptorクラス/ModuleDescriptor.Exportsクラス/ModuleDescriptor.Opensクラス/ModuleDescriptor.Requiresクラス

前述したようにaccesFlagsメソッドが追加されました。

  • Set<AccessFlag> accessFlags()

 

java.base/java.lang.reflectパッケージ

リフレクションのためのjava.lang.reflectパッケージですが、すでに出てきているAccessFlag列挙型が追加されています。また、それに関したクラスやメソッドも追加されました。

AccessFlag列挙型

AccessFlag列挙型はアクセス修飾子などを定義しています。これらの定数はクラスファイルのヘッダのaccess_flagsで指定するACC_X (XにはPUBLICなどのアクセス修飾子などが入ります)に対応しています。

定数はPUBLICやSTATIC, INTERFACEなど23種類ありますが、量が多いのでここでは省略します。そういうものがあると知って入れば十分だと思います。

メソッドも7種類定義されていますが、まぁ使うことはないでしょう。

 

AccessFlag.Location列挙型

AccessFlagが修飾している対象を表す列挙型がAccessFlag.Location列挙型です。

定数はCLASS, FIELD, MODULEなど9種類があります。

 

ClassFileFormatVersion列挙型

この列挙型も新しく導入された列挙型ですが、似たような列挙型はすでにありました。それが、ソースファイルのバージョンを示すjavax.lang.model.SourceVersion列挙型です。

ClassFileFormatVersion列挙型はクラスファイルのバージョンを示す列挙型です。

しかし、使用している定数はSourceVersion列挙型と同じで、RELEASE_1などRELEASE_の後にバージョンの数字が入る形式になっています。

 

Executableクラス/Fieldクラス/Memberクラス/Parameterクラス

この4クラスも各クラスにaccessFlagsメソッドが追加されました。

  • Set<AccessFlag> accessFlags()

 

java.base/java.netパッケージ

URLクラスにメソッドが1つ追加されました。

URLクラス

URLクラスではファクトリメソッドが追加されています。また、それに伴いコンストラクタがDeprecatedになりました。forRemoval=trueではありませんが、近い将来削除される可能性があるため、今後は使うのを控えた方がいいと思います。

  • static URL of(URI uri, URLStreamHandler handler)

基本的にはURLはURIから作るわけですが、通常はURI.toURLを使います。

ところで、今までのURLオブジェクトをコンストラクタで生成するにはURLStreamHandlerクラスが使われます。そして、URLのパースや検証が実際に使われる時まで遅延されることがあります。

URLのコンストラクタと同様に生成するときにofメソッドを使用すればいいということです。

 

java.base/java.nio.channelsパッケージ

FileChannelクラス

FileChannelクラスはmapメソッドを使用してファイルのコンテンツをメモリにマップすることができますが、FFM APIを使ったmapメソッドも導入される予定です。今はPreview APIになっています。

 

java.base/java.nio.file.spiパッケージ

FileSystemProviderクラス

FileSysstemProviderクラスはファイルシステムの各種操作を行うためのクラスで、FIlesクラスのメソッド群の処理はこのクラスに委譲されます。

逆にいうと、Filesクラスでファイルに対する操作のメソッドはFileSystemProviderクラスにもあるということです。たとえば、FilesクラスのcreateDirectoryメソッドは次のようになっています。

public static Path createDirectory(Path dir, FileAttribute<?>... attrs)
        throws IOException {
    provider(dir).createDirectory(dir, attrs);
    return dir;
}

providerメソッドがFileSystemProviderオブジェクトを取得するためのメソッドです。

このFileSystemProviderクラスに2つ追加されました。というか、何でこのメソッドがなかったのだろうかという感じです。

  • boolean exists (Path path, LinkOption... options)
  • <A extends BasicFileAttributes> A readAttributesIfExists(Path path, Class<A> type, LinkOption... options)

メソッド名の通り、ファイルの有無をチェックするメソッドと、アトリビュートがあれば読み込むメソッドです。

 

java.base/java.textパッケージ

APIの追加はないのですが、BreakIteratorクラスの動作が変更されています。

BreakIteratorクラス

BreakIteratorクラスは文字列の境界の位置を探索するためのクラスです。内部的にはCharacterIteratorインタフェースを使用して文字列をスキャンしていきます。

Java 19の JEPで語れない の中でフライングしてしまったのですが、このBreakIteratorクラスの動作が変更されました。

具体的には、Java 19までのBreakIteratorクラスではUnicodeのゼロ幅接合子などの境界を正しく検出することができませんでした。

BreakIteratorクラスが使えなかったので、ゼロ幅接合子などを含む文字列を分割するには正規表現の\b{g}を使うしかありませんでした。

これに対し、Java 20ではゼロ幅接合子なども正しく扱えるようになっています。

文字の境界に関してはUnicode Standard Annex #29の Unicode Text Segmentation を参照してください。

 

java.base/java.util.concurrentパッケージ

Fork/Join Framework関連クラスでAPIが追加されました。

 

ForkJoinPoolクラス

タスクを登録するsubmit関連のメソッドが追加されました。

  • <T> ForkJoinTask<T> externalSubmit(ForkJoinTask<T> task)

ForkJoinPoolでプールしているワーカースレッドではない外部のスレッドからタスクを登録するためのメソッドです。

submitメソッドはForkJoinPoolのワーカースレッドでも外部のスレッドでも、どちらからでも登録できます。外部のスレッドから登録した場合はexternalSubmitメソッドと同じ動作です。

なので、なぜ今になってexternalSubmitメソッドが追加されたのかイマイチよく分からないのです。

 

ForkJoinWorkerThreadクラス

Fork/Join FrameworkはタスクスケジューリングにWork-Stealingを使用しています。Work-Stealingでは各ワーカースレッドがタスクキューを持ち、キューの先頭のタスクから実行を行います。

もし、自身のタスクキューが空の場合は、他のワーカースレッドのタスクキューの最後からタスクを取り出して(盗んで)実行します。

ForkJoinWorkerThreadクラスではこのタスクキューに登録されているタスクの個数を返すメソッドが追加されました。

  • int getQueuedTaskCount()

 

java.base/java.util.regexパッケージ

java.util.regexパッケージのAPI追加は普通の開発者にとって唯一役に立つAPIのような気がしますw

 

MatchResultインタフェース

MatchResutlインタフェースを直接使用することはないとは思いますが、MatcherクラスがMatchResultインタフェースを実装しているので意識はしていなくても使用しているはずです。

MatchResultインタフェースでは5つのメソッドが追加されました。すべてがデフォルトメソッドなのですが、namedGroupsメソッドとhasMatchメソッドはUnsupportedOperationException例外をスローする実装になっています。

だったらデフォルトメソッドにしないでほしいですけど、すでにリリースされていたインタフェースにメソッドを追加するとなるとこうするしかないんでしょうね。

  • default int end(String name)
  • default String group(String name)
  • default boolean hasMatch()
  • default Map<String, Integer> namedGroups()
  • default int start(String name)

endメソッド、groupメソッド、startメソッドは正規表現のグループに関するメソッドです。

この3種類のメソッドの引数の型はすべて文字列です。引数名のnameは正規表現の名前付きグループに対する名前となっています。

実をいうと、これらのメソッドはJava 19まではMatcherクラスで定義されているメソッドでした。それらが、MatcherクラスではなくMatchResultインタフェースで定義されることになったわけです。

引数のないメソッド、およびint型が引数の型のメソッドはMatchResultインタフェースで定義されていたので、すべてMatchResultインタフェースのメソッドということになりました。

namedGroupsメソッドも正規表現のグループに関連するメソッドです。

名前付きグループを使用した場合、名前とグループ番号を対応づけたマップを返します。名前を使用していない場合は空のマップが返ります。

JShellで動作を確認してみます。

jshell> var pattern = Pattern.compile("(?\\d+)(?[a-z]+)(?\\d+)")
pattern ==> (?<prenum>\d+)(?<alphabet>[a-z]+)(?<postnum>\d+)

jshell> var matcher = pattern.matcher("12ab345e67fgh890i")
matcher ==> java.util.regex.Matcher[pattern=(?<prenum>\d+)(?< ... +) region=0,17 lastmatch=]

jshell> matcher.find()
$3 ==> true

jshell> matcher.namedGroups()
$4 ==> {prenum=1, postnum=3, alphabet=2}

jshell> matcher.group("alphabet")
$5 ==> "ab"

jshell> matcher.group(2)
$6 ==> "ab"
	  
jshell> matcher.start("postnum")
$7 ==> 4
	  
jshell> matcher.start(3)
$8 ==> 4

jshell> matcher.end("prenum")
$9 ==> 2

jshell> matcher.end(1)
$10 ==> 2

jshell>

数字 小文字アルファベット 数字 と連なるパターンを作ってみました。グループは()で、グループ名は?<>で表します。

したがって、グループ名がprenumばグループ1、名前がalphabetはグループ2、postnumがグループ3になります。

namedGroupsメソッドを使用すると、この関係を持ったマップが返ることを確認できます。

今までグループの個数はgroupCountメソッドで分かりましたが、名前一覧は取得できなかったので名前付き正規表現が扱いやすくなるはずです。

 

最後のhasMatchメソッドはfindメソッドなどを複数回使用してマッチさせた後でもマッチしていたかどうかを返すメソッドです。もちろん、過去だけでなく現在の状態も加味して結果を返します。

 

Matcherクラス

MatcherクラスはMatchResultインタフェースを実装しているので、デフォルトメソッドのnamedGroupsメソッドとhasMatchメソッドをオーバーライドしています。

使用例はMatchResultインタフェースで示した通りです。

 

Patternクラス

PatternクラスでもnamedGroupsメソッドが追加されました。

  • Map<String, Integer> namedGroups()

PatternクラスではMatcherオブジェクトを内部的に使用して正規表現にマッチしているかどうかを調べることができます。そんな場合に使用します。

とはいうものの、あまりこういう使い方はしないような気がするんですよね...

 

java.base/javax.net.sslパッケージ

 

SSLParametersクラス

SSLParametersクラスはSSLというかTLS接続のパラメータを扱うためのクラスです。

SSLSocketオブジェクト、SSLServerSocketオブジェクトもしくはSSLEngineオブジェクトから取得します。

SSLParametersクラスではキー交換時に使用する名前付きグループに関するメソッドが2種類追加されました。

  • String[] getNamedGroups()
  • void setNamedGroups(String[] namedGroups)

 

 

Java 20のAPI変更について紹介しましたが、意外に少ないですね。しかも、普通に使いそうなAPIは正規表現のAPIぐらいのような気がします。

Java 20のAPI変更は少ないですけど、Java 21はjava.utilパッケージのコレクションにメソッドが多く追加される予定です(JEP 431)。

JEP 431: Sequenced CollectionsはPreview JEPではなくStandard JEPなので、そのまま正式リリースされる予定です。Java 21の時のこのシリーズはJEPには含まれていますけども、JEP 431も含めて紹介するつもりです。