2021/03/16

JEPでは語れないJava 16

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

半年ぶりのblog更新の季節がやってきました。春と秋はJavaのアップデートの季節です。

アメリカ西海岸時間の3月16日にJava SE 16がリリースされます。16日に16が出るわけですが、17は17日に出るのでしょうかw

Java 16は、LTSであるJava 17の1つ前のバージョンということがあり、いろいろと機能盛りだくさんです。IncubatorやPreviewのままJava 17に入れると、結局使えなくなってしまうので、そういったものはJava 16で入れておかなくてはいけないわけです。

Java 17で正式な機能にならないと、3年後になってしまいますから。

残念なのはswitch式でのパターンマッチが結局入らなかったことでしょうか。instanceofのパターンマッチは使えるのですが、本命はswitch式の方なので。

Java 16で取り入れられるJEPは16個。なかなか多いですね。

この中にはgitへの変更やGitHubへの移行、ビルド環境としてC++ 14の採用なども含まれているので、Javaの機能としては13個のJEPです。

また、Java 16ではAPIの変更が多いというのも特徴的です。

Project Panama関連で3つのJEP、それ以外にもUnixドメインソケットのJEPと、4つもAPIに関するJEPがあります。こんなにAPIに関するJEPがあったのは、JEPのプロセスになってからはじめてのような気がします。

JEPにはなっていないのですが、それ以外にも大きなAPIの変更があります。

ということで、ここ数年でAPIの変更が最も大きいバージョンになりそうです。

ただし、Panama関連のAPIの追加はIncubatorであり、規模も大きいので、このエントリーでは省略させていただきます。

また、いつものようにセキュリティ系のAPIはちゃんと理解していないので、省略します。

 

廃止になったAPI

Java 16で廃止されたAPIは1つだけです。

コンストラクタ

Java 14でforRemoval=trueになったコンストラクタが1つだけ削除されました。

  • javax.tools.ToolProvider()

ToolProviderクラスはコンパイラなどを取得するために使用するクラスですが、メソッドはすべてstaticメソッドで、インスタンスを生成する必要がないため、削除されました。

正式なAPIの廃止はこれだけなのですが、Preview機能に関するメソッド廃止というか変更もありました。それは、APIの追加のところで説明します。

 

廃止予定のAPI

Java 16では廃止予定APIの追加はJEP 390 Warnings for Value-Based Classesに関連するものが多いです。

JEP 390はプリミティブのラッパークラスなどのValue-Based Classのコンストラクタを廃止予定にし、使用時に警告を出すというものです。これはProject Valhallaに関連したJEPになります。

ちなみに、ラッパークラスのコンストラクタはすでに@Deprecatedだったのですが、foreRemovale=trueが追加されたということです。

メソッド

  • java.lang.ThreadGroup.destroy
  • java.lang.ThreadGroup.isDeamon
  • java.lang.ThreadGroup.isDestroyed
  • java.lang.ThreadGroup.setDaemon
  • java.lang.ThreadGroup.stop
  • java.awt.color.ICC_Profile.finalize
  • java.awt.image.ColorModel.finalize
  • java.awt.image.IndexColorModel.finalize

コンストラクタ

  • java.lang.Boolean(boolean value)
  • java.lang.Boolean(String s)
  • java.lang.Byte(byte value)
  • java.lang.Byte(String s)
  • java.lang.Character(char value)
  • java.lang.Short(short value)
  • java.lang.Short(String s)
  • java.lang.Integer(int value)
  • java.lang.Integer(String s)
  • java.lang.Long(long value)
  • java.lang.Long(String s)
  • java.lang.Float(float value)
  • java.lang.Float(String s)
  • java.lang.Double(double value)
  • java.lang.Double(String s)
  • java.net.URLDecoder()
  • javax.management.relation.RoleStatus()

ThreadGroupクラスはresumeメソッドやsuspendメソッドはすでにforRemovalがtrueになっていましたが、それに追加してdestroyメソッドなどが廃止予定に追加されています。

AWTの3つのメソッドはいずれもファイナライザーなので、使わない方がいいということは分かっていいので、標準ライブラリでも削除予定になっているわけです。

 

追加されたAPI

Java 15までは追加されたAPIがjava.baseモジュールとjava.compilerモジュールだけというのが続いていたのですが、Java 16ではこれ以外にjava.desktopモジュールなどでもAPIの追加が行われています。

また、Java 11以降で追加されたAPIはほとんどが言語仕様に関連したものが多く、一般の開発者が使うことがないようなAPIばかりでした。しかし、Java 16では久しぶりによく使いそうなメソッドが追加されています。

今回はその中でも重要度の高いStream APIなどについてはじめに紹介して、その後はいつも通りモジュール/パッケージのアルファベット順に紹介していきます。。

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

Stream APIでは、プリミティブ系のStreamに1つ、Streamインタフェースには2種類のメソッドが追加されました。また、これにともなって、3つの内部インタフェースが追加されています。

Streamインタフェース

Streamインタフェースでは2種類のメソッドが追加されたと書きましたが、4つは関連したメソッドで総数としては5つのメソッドが追加されました。

  • default <R> Stream<R> mapMulti(BiConsumer<? super T, ? super Consumer<R>> mapper)
  • default DoubleStream mapMultiToDouble(BiConsumer<? super T, ? super DoubleConsumer> mapper)
  • default IntStream mapMultiToInt(BiConsumer<? super T, ? super IntConsumer> mapper)
  • default LongStream mapMultiToLong(BiConsumer<? super T, ? super LongConsumer> mapper)
  • default List toList()

いずれもdefaultメソッドです。インタフェースへのメソッド追加なので、そうならざるをえないわけですね。

戻り値を見ていただければ分かる通り、mapMulti系が中間操作、toListメソッドが終端操作です。

mapMultiメソッドの引数はBiConsumerインタフェースですが、これは引数が2つ、戻り値なしの関数型インタフェースです。中間操作なのに、戻り値がないというのはどういうことなんでしょう。

どういう動作をするのかはソースを見てもらうのが手っ取り早いでしょう。

    default <R> Stream<R> mapMulti(BiConsumer<? super T, ? super Consumer<R>> mapper) {
        Objects.requireNonNull(mapper);

        return flatMap(e -> {
            SpinedBuffer<R> buffer = new SpinedBuffer<>();
            mapper.accept(e, buffer);
            return StreamSupport.stream(buffer.spliterator(), false);
        });
    }

mapMultiメソッドの引数のBiConsumerインタフェースは引数が2つですが、一方はストリームを流れてくる値、もう一方がConsumerオブジェクトです。

このConsumerオブジェクトが上記のソースだと変数bufferで表されるSpinedBufferオブジェクトです。このSpinedBufferクラスはBufferという名前がつくように何らかのバッファだと考えられます。そして、SpinedBufferクラスのacceptメソッドでバッファに要素を追加していきます。

そして、StreamSupportクラスのstreamメソッドでバッファの要素をストリーム化しているわけです。

ということで、BiConsumerインタフェースのラムダ式では、流れてきた要素に対し何らかの処理を行い、必要であれば第2引数のConsumerオブジェクトの顔をしたバッファに要素を追加していきます。

すると、次の処理にはConsumerオブジェクトに追加した要素が流れてくることになります。

たとえば、単純に条件に合致した要素だけを取り出す例で考えてみましょう。以下のコードは10以下の数だけを集める処理です。

    var numbers = List.of(0, 20, 10, 5, 50, 3);

    var result = numbers.stream()
            .mapMulti((num, consumer) -> {
                if (num < 10) consumer.accept(num);
            })
            .collect(Collectors.toList());
  

変数resultには0, 5, 3が入ります。

この例だとfilterメソッドを使ったのと変わりないですが、ストリームの要素がバラバラで統一的に処理できないような時に使うことができるはずです。

mapMulti系の他の3つのメソッドはいずれもプリミティブ系のストリームに変更する場合に使用するもので、使い方としてはmapMultiメソッドと同じです。

最後のtoListメソッドはストリームをリストに変換するメソッドです。単純ですが、使用機会はとても多いはずです。

実施的には、collectメソッドにCollectors.toList()を引数にした場合と同じ動作です。

これも実装を見てみましょう。

    default List<T> toList() {
        return (List<T>) Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())));
    }
  

Collectors.toListメソッドではなく、toArrayメソッドを使って変更不可能なリストを作成しています。

IntStream/LongStream/DoubleStreamインタフェース

プリミティブ系のストリームにもmapMultiメソッドが追加されています。

また、BiConsumerインタフェースに対応するように内部インタフェースがそれぞれ追加されています。

  • IntStream.IntMapMultiConsumer
  • LongStream.LongMapMultiConsumer
  • DoubleStream.DoubleMapMultiConsumer

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

まさかのjava.netパッケージにクラスが追加されましたよ。

これはJEP 380: Unix-Domain Socket Channelsに関連したクラスです。

UnixDomainSocketAddressクラス

Unixドメインソケットは同一PC内でのプロセス間通信を行う時に、高効率で通信できるソケットです。

JavaではTCP/IP通信と同じように扱うことができます。違いはアドレスを表すクラス、つまりこのUnixDomainSocketAddressクラスだけです。

UnixDomainSocketAddressクラスは、InetSocketAddressクラスと同様にSocketAddressクラスのサブクラスです。

Unixドメインソケットではファイルパスで指定して通信します。このため、UnixDomainSocketAddressクラスもインスタンシーエションはファイルパスで行います。

    var address1 = UnixDomainSocketAddress.of("/tmp/socketfile1");
    var address2 = UnixDomainSocketAddress.of(Path.of("tmp/socketfile2"));
  

ofメソッドは引数がStringクラスとPathインタフェースの2種類があります。

UnixDomainSocketAddressオブジェクトが生成できれば、後はTCP/IP通信と同様にSocketChannelなどで使用できます。

StandardProtocolFamily列挙型

Unixドメインソケットが追加されたので、StardardProtocolFamily列挙型にも定数が追加されました。

  • UNIX

通常は、この列挙型を使うことはほぼないはずです。

 

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

Buffer系のクラスにメソッドが追加されました。いずれも同じメソッドなのですが、引数の型がそれぞれ異なります。

ByteBuffer/CharBuffer/ShortBuffer/IntBuffer/LongBuffer/FloatBuffer/DoubleBufferクラス

いずれのクラスもputメソッドのオーバーロードが追加されています。以下にはByteBufferクラスの場合のシグネチャを示します。

  • ByteBuffer put(int index, ByteBuffer src, int offset, int length)

今までBuffer系のクラスにはput(int index, byte[] src, int offset, int length)メソッドとput(ByteBuffer src)メソッドはありました。しかし、書き込み位置と範囲を指定できるputメソッドがなかったのです。

まぁ、ByteBufferクラスだけを引数にして、適切なpositionとlimitを設定しておけば、同じ動作はできるんですけどね。分かりやすさを優先させたんでしょう。

 

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

ここからはそれほど使わないであろうAPIの変更をアルファベット順に説明していきます。

まずは、java.langパッケージですが、こちらはJEP関連のメソッド変更がありました。

Classクラス

Clsssクラスでは、JEP 360 Sealed Classes関連のAPI変更がありました。変更というのは名前とシグネチャが変わったということです。

  • Class[] getPermittedSubclasses()

getPermittedSubclassesメソッドが追加された代わりに、Java 15で導入されたpermittedSubclassesメソッドは廃止されました。

この2つのメソッドの違いは戻り値の型です。

permittedSubclassesメソッドはClassDesc[]だったのが、getPermittedSubclassesメソッドではClass[]になっています。

当然のことながら使いやすいのは、ClassDescクラスよりもClassクラスということだったのでしょう。

IndexOutOfBoundsException例外

コンストラクタが1つ追加されました。

  • IndexOutOfBoundsException(long index)

indexがintのコンストラクタはあったのですが、longはありませんでした。というか、配列のインデックスはintだけなので、配列を想定していたらlongは必要なかったわけです。

配列のインデックスがlongになることはないでしょうけど、Bufferとかはlongのインデックスがほしいと思うことはちょっとあります。さて、どうなるでしょうねw

 

 

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

indy/condy関連のパッケージですが、JEP 371 Hidden Classに関するAPIが追加されています。

MethodHandlesクラス

MethodHandlesクラスはIndyでメソッドをコールするために使用するMethodHandleクラスのユーティリティクラスです。そんなMethodHandlesクラスにメソッドが2つ追加されました。

  • static <T> T classData(MethodHandles.Lookup caller, String name, Class<T> type)
  • static <T> T classDataAt(MethodHandles.Lookup caller, String name, Class<T> type, int index)

どちらもIndyがはじめてコールされた時に使用されるブートストラップメソッドからコールされることを想定しています。簡単に言うと、引数のLookupオブジェクトから第3引数で指定されたクラスを返すというメソッドです。Hidden Classを作成した時を想定しています。まぁ、普通は使うことはないでしょうね。

MethodHandles.Lookupクラス

LookupクラスはindyやcondyでメソッドなどをルックアップするためのfindXXXメソッドを定義しています。

このLookupクラスにJava 15でdefineHiddenClassメソッドが追加されました。これに関連して、もう1つメソッドが追加されました。

他にラムダ式のように動的に生成したクラスを定義するためにdefineClassメソッドなどがあります。このdefineClassメソッドと同様なメソッドがHidden Class関連で追加されています。

  • MethodHandles.Lookup defineHiddenClass(byte[] bytes, Object classData, boolean initialize, MethodHandles.Lookup.ClassOption... options)

defineHiddenClassメソッドとの違いは、第2引数のclassDataです。これがクラスデータと呼ばれるものです。APIドキュメントにはpre-intialized class dataとなっています。

また、定数も1つ追加されました。

  • ORIGINAL

これはlookupModeで使用する定数ですが、他のMODULEとかPRIVATEなどの定数との違いはよく分かりません。

VarHandleクラス

VarHandleクラスは、MethodHandleクラスの変数版です。

このVarHandleクラスをinvokeできるかどうかに関するメソッドが3つ追加されました。

  • boolean hasInvokeExactBehavior()
  • VarHandle withInvokeBehavior()
  • VarHandle withInvokeExactBehavior()

VarHandleオブジェクトに対してinvokeを行うのはMethodHandleクラスですが、型を厳密に一致させる必要があるのがinvokeExactメソッド、必要に応じて引数や戻り値の変換もできるのがinvokeメソッドです。

invokeExact/invokeメソッドの引数に指定するのがVarHandleクラスですが、invokeExactメソッドを使用する時には厳密性があるかどうかがチェックされます。このチェックにhasInvokeExactBehaiviorメソッドが使われます。

そして、withInvokeBehaiviorメソッドとwithInvokeExactBehaiviorメソッドでinvocation時の振る舞いを変更することができます。

 

java.base/java.time.formatパッケージ

java.time.formatパッケージはDate and Time APIのフォーマッタに関するパッケージです。

DateTimeFormatterBuilderクラス

DateTimeFormatterBuilderクラスはDateTimeFormatterオブジェクトを生成するためのビルダークラスです。

そこに、朝、昼、夜などのDay Periodを表す文字列を追加するメソッドが追加されました。

  • DateTimeFormatterBuilder appendDayPeriodText(TextStyle style)

実際に試してみましょう。

jshell> var time = LocalTime.of(15, 0)
time ==> 15:00

jshell> var formatter = new DateTimeFormatterBuilder().
   ...> appendDayPeriodText(TextStyle.FULL).toFormatter()
formatter ==> DayPeriod(FULL)

jshell> System.out.println(formatter.format(time))
昼

というように15時だと、"昼"とフォーマットされました。ところが、FULLでもNARROWでもSHORTのどれでも"昼"になってしまうんですよね。英語だとすべてin the afternoonになってしまいます。

これはこれから変わるんでしょうか?

 

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

Stream APIが変更されたのだから、java.utilパッケージのコレクションに関連したのが追加されたのかと思ったのですが、空振りでしたw

Objectsクラス

Objectsクラスには範囲のチェックを行うcheckIndexメソッドやcheckFromIndexSizeメソッドがあるのですが、これに関連して3種類のオーバーロードが追加されました。

  • static long checkFromIndexSize(long fromIndex, long size, long length)
  • static long checkFromToIndex(long fromIndex, long toIndex, long length)
  • static long checkIndex(long index, long length)

違いは引数の型がintではなく、longになったということです。IndexOutOfBoundsException例外に関連しているんでしょうね。

 

java.compiler/javax.lang.modelパッケージ

毎度毎度のjavax.lang.modelパッケージです。

SourceVersion列挙型

Java 15に対応する定数が追加されました。

  • RELEASE_16

 

java.desktop/javax.swingパッケージ

SwingにAPIの変更があるなんてビックリです。

JPasswordFieldクラス

パスワード用のテキストフィールドを表すJPasswordFieldクラスですが、スーパークラスのsetTextメソッドがオーバーライドされています。

  • void setText(String t)

なぜこのタイミングでこの変更が行われたのか、まったく分かりませんw

JSlider.AccessibleJSliderクラス

スライダーを表すJSliderクラス用のアクセシビリティ対応のクラスがAccessibleJSliderクラスですが、ChangeListenerインタフェースを実装しています。

そのChangeListenerインタフェースの定義しているメソッドがオーバーライドされているのですが、これ追加なのでしょうか? 単にAPIドキュメントに載っていなかったというだけのような気が...

  • void stateChanged(ChangeEvent e)

この他にもLook & Feelに関する部分でオーバーライドがいくつか追加されているのですが、省略します。

 

java.logging/java.util.loggingパッケージ

Logging APIにスレッド関連のAPIが追加されました。

LogRecordクラス

ログの1行に相当するLogRecordクラスにはスレッドのIDを記録できたのですが、これに関連して2つのメソッドが追加されました。

  • long getLongThreadID()
  • LogRecord setLongThreadID(long longThreadID)

これまで、スレッドのIDはintだったのですが、OSによってはlongのものがあるということでlongも扱えるようになったということのようです。Project FiberのVirtual Threadで大量のスレッドを扱えるようになるからかと思ったのですが、違っていたようですw

 

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

Java 11で導入されたHTTP Clientがjava.net.httpモジュールのjava.net.httpパッケージで定義されています。

HttpRequestクラス

HttpRequestオブジェクトを生成するためのビルダークラスがHttpRequest.Builderクラスです。このBuilderクラスのファクトリメソッドが1つ追加されました。

  • static HttpRequest.Builder newBuilder(HttpRequest request, BiPredicate<String, String> filter)

すでに存在するHttpRequestオブジェクトを再利用して、新しいHttpRequestオブジェクトを生成するためのビルダーのファクトリメソッドです。

第1引数が既存のHttpRequestオブジェクト、第2引数が第1引数のHttpRequestオブジェクトのヘッダーを残すかどうかを決めるフィルタになります。

BiPredicateインタフェースなので引数が2つ、戻り値の型がbooleanですが、第1引数がヘッダ名、第2引数にその値が指定されます。残すのであればture、残さないのであればflaseを返します。

HttpRequest.BodyPublishersクラス

POSTでHttpRequestオブジェクトを生成する時のボディを作成するのがBodyPublishersクラスです。

BodyPublishersクラスはstaticメソッドのファクトリメソッドだけが定義されているのですが、そのファクトリメソッドが1つ追加されました。

  • static HttpRequest.BodyPublisher concat(HttpRequest.BodyPublisher... publishers)

複数のBodyPublishersオブジェクトを連結させて、複数のボディを生成するBodyPublishersオブジェクトを生成するファクトリメソッドです。

 

ということで、一通りAPIの変更をさらってきましたが、LTSの1つ前のバージョンというのが如実に表れているような気がします。JEPで策定した以外の変更も多かったですね。

逆にJava 17は変更が少ないのかもしれません。少なくともIncubatorやPreviewは入れにくいでしょうし。

ということは、Java 16が使いこなせていれば、LTSのJava 17に移行するのも楽かもしれませんね。

2020/09/09

JEPでは語れないJava SE 15

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

恒例になってしまった半年ぶりのblog更新ですw

アメリカ西海岸時間の9月15日にJava SE 15がリリースされます。今年は21日じゃないんですね。ちょっと残念。

Javaとは関係ないのですが、今年から9月21日はセプテンバーの日になってます。分からない人はEarth Wind & Fireでググってみてくださいw

APIに関しては、Java 14でIncubator Moduleとして提案されたJEP 383 Forein-Memory Access APIがSecond Incubatorになっています。うまくいけば、Java 16で正式になって、次のLTSに含まれそうです。

Project Panamaに関してはJEP 389 Forien Linker APIも提案されていて、これがJava 17に入るとJNI要らなくなるんですよね。すでにPanamaで提供されているjextractを使えばJNIはいらないですけど、JEP 389を使えばjextractも要らなくなるはず。

また、JEP 371 Hidden ClassesもAPIに関するJEPです。

これについては、後述します。

追加されるAPIもあれば、削除されるAPIもあります。

Java 15で削除されたのがJavaScriptエンジンのNashorn (JEP 372 Remove the Nashorn JavaScript Engine)。また、Deprecatedになったのが、RMI Activation (JEP 385 Deprecate RMI Activation for Removal)。さすがに、RMIは役目を終えていると思います。

 

JEPに関する変更はまた別の機会に書くとして (書けるかどうかはわからないですけど)、本エントリーはJEPになっていないものも含めてAPIの変更についてまとめます。

今回もABC順にならんでいます。同じように、セキュリティ系のAPIはちゃんと理解していないので、省略してます。

 

廃止になったAPI

Java 15ではNashornが廃止されましたが、Nashornはjdkではじまるモジュール(jdk.scripting.nashorn)なので、標準API的には変化がありません。

その他の廃止にAPIは定数が1つ、コンストラクタが2つととても少ないです。

フィールド

JMX Remote関連の定数が1つ削除されました。

  • javax.management.remote.rmi.RMIConnectorServer.CREDENTIAL_TYPES

CREDENTIAL_TYPESの代わりにCREDENTIALS_FILTER_PATTERNを使うようにします。とはいうものの、JMXをRMIで接続するコードを書くことは、自分で監視用ツールを作るということでもない限り使わないでしょうね。

コンストラクタ

Java 14でforRemoval=trueになったコンストラクタが早々と削除されました。

  • java.lang.invoke.ConstantBootstraps.<init>
  • java.lang.reflect.Modifier.<init>

ConstantBootstrapsクラスはCONDYのブートストラップのためのユーティリティクラスですが、もともとstaticなクラスメソッドしか定義されていないので、コンストラクタがある方がおかしかったわけです。

Modifierクラスもモディファイアの定数と、クラスメソッドのisXXXメソッド群を定義しているクラスなので、コンストラクタは必要ありません。基本的にはClass.getModifiersメソッドやMember.getModifiersメソッドで返されるint値を引数にして、isXXXメソッドでチェックするという使い方です。

このため、これらのコンストラクタが削除されたとしても、影響はないでしょう。

 

廃止予定のAPI

Java 15で追加された廃止予定APIは、前述したRMI Activationに関するものです。

パッケージ

  • java.rmi.activation

クラス/インタフェース

  • java.rmi.activation.Activatable
  • java.rmi.activation.ActivationDesc
  • java.rmi.activation.ActivationGroup
  • java.rmi.activation.ActivationGroup_Stub
  • java.rmi.activation.ActivationGroupDesc
  • java.rmi.activation.ActivationGroupID
  • java.rmi.activation.ActivationID
  • java.rmi.activation.ActivationInstantiator
  • java.rmi.activation.ActivationMonitor
  • java.rmi.activation.ActivationSystem
  • java.rmi.activation.Activator

例外

  • java.rmi.activation.ActivateFailedException
  • java.rmi.activation.ActivateException
  • java.rmi.activation.UnknownGroupException
  • java.rmi.activation.UnknownObjectException

 

追加されたAPI

Java 14で追加されたAPIのほとんどがjava.baseモジュールで、その他はjava.compilerモジュールだけです。

しかも、一番追加が多かったパッケージはjava.langパッケージ。とはいうものの、恒例のUnicodeのブロックとスクリプトがあるせいですけど。

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

java.langパッケージではクラス定義の変更や、前述したUnicode関連の変更、またJEP関連のメソッド追加があります。

Boolean/Byte/Character/Shortクラス

Java 12でCondyが導入されたため、新しく導入されたのがConstableインタフェースです。すでに、Java 12でIntegerクラスやDoubleクラスなどはConstableインタフェースを実装するように変更されていました。

Boolean/Byte/Character/Shortクラスはその時にもれていたプリミティブ型のラッパークラスで、Java 15でConstableインタフェースを実装するように変更されました。

Constableインタフェースの実装に伴い、以下のメソッドが追加されています。

  • describeConstable()

まぁ、普通の開発者は使わないでしょうけど。

CharSequenceインタフェース

Stringクラスでは定義されていたisEmptyメソッドが、CharSequenceインタフェースで定義されるようになりました。

  • boolean isEmpty()

isEmptyメソッドはデフォルトメソッドなので、CharSequenceインタフェースの実装クラスは変更なしです。また、その実装はStringクラスのそのままです。

Character.UnicodeBlockクラス

Java 14でサポートしていたUnicodeのバージョンは12.1ですが、Java 15ではUnicode 13をサポートするようになりました。

Unicode 13といえば、最近セブンイレブンでも売り出して話題のビャンビャン麺のビャンの文字が入るわけです。現時点で最も画数の多い漢字ですw

それはそうと、Unicodeのバージョンがあがると、当然のごとくブロックとスクリプトが追加されます。

追加されたブロックを示す定数は以下の8種類です。

  • CHORASMIAN
  • CJK_UNIFIELD_IDEOGRAPHS_EXTENSION_G
  • DIVES_AKURU
  • KHITAN_SMALL_SCRIPT
  • LISU_SUPPLEMENT
  • SYMBOLS_FOR_LEGACY_COMPUTING
  • TANGUT_SUPPLEMENT
  • YEZIDI

Character.UnicodeScript列挙型

スクリプトの定数は4種類追加されました。

  • CHORASMIAN
  • DIVES_AKURU
  • KHITAN_SMALL_SCRIPT
  • YEZIDI

Classクラス

Clsssクラスには2つのJEPに関連するメソッドが追加されました。

  • boolean isHidden()
  • boolean isSealed()
  • ClassDesc[] permittedSubclasses()

isHiddenメソッドがJEP 371 Hidden Classに関連したメソッドで、他の2メソッドがJEP 360 Sealed Classes関連のメソッドです。

isHiddenメソッドは、クラスがHidden Classかどうかを調べるメソッドです。Hidden Classについては、後述するMethodHandlesクラスの時に説明します。

ここでは、Sealed Classについて簡単に説明しておきましょう。

Sealed Classは継承を限定できるクラスです。

Sealed Classを定義するにはclassの前にsealedをつけ、派生させるクラスをpermitsの後に列挙します。

たとえば、Shpaeクラスを派生できるのはCircleクラスとRectangleクラスだけとしましょう。

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

jshell> sealed class Shape permits Circle, Rectangle {}
|  次を作成しました: クラス Shape。しかし、 class Circle, and class Rectangleが宣言されるまで、参照できません

jshell> final class Circle extends Shape {}
|  次を作成しました: クラス Circle。しかし、 class Shapeが宣言されるまで、参照できません

jshell> final class Rectangle extends Shape {}
|  次を作成しました: クラス Rectangle

jshell> final class Triangle extends Shape {}
|  エラー:
|  クラスはシール・クラスShapeを拡張できません
|  final class Triangle extends Shape {}
|  ^-----------------------------------^

jshell>

ShapeクラスはCircleクラスとRectangleクラスのみ派生できるように定義したので、それ以外のTriangleクラスを派生させようとしてもコンパイルエラーになります。

なぜ、こんな機能が必要かというと、今後導入されるswitch式でのパターンマッチングなどに使えるからです。派生クラスが制限されていれば、default句が必要なくなるからです。

ちなみに、CircleクラスやRectangleクラスがfinal classなのは、Circleクラスをさらに派生させたクラスを作成できてしまうとsealする意味がなくなってしまうからです。

ただし、final classではなく、多重にsealed classにすることはできます。

さて、ここまでくれば、isSealedメソッドがSealed Classかどうかを調べるクラスだということはすぐにわかりますね。

jshell> Shape.class.isSealed()
$4 ==> true

jshell> Circle.class.isSealed()
$5 ==> false

jshell>

ShapeクラスはisSealedメソッドでtrueが返りますが、Circleクラスはfalseになります。

もう1つのpermittedSubclassesメソッドはSelaed Classを派生できるクラスの一覧を返すメソッドです。

jshell> Shape.class.permittedSubclasses()
$6 ==> ClassDesc[2] { ClassDesc[Circle], ClassDesc[Rectangle] }

jshell>

Sealed Classではない普通のクラスでpermittedSublassesメソッドをコールすると、空の配列が帰ります。

まぁ、Sealed Classを使うことはあっても、普通はpermittedSubclassesメソッドを使わないでしょうけど。

Mathクラス/StrickMathクラス

2つのクラスとも同じメソッドが追加されました。

  • static int absExact(int a)
  • static long absExact(long a)

どちらも実装は同じです。

普通のabsと何が違うかというと、引数がMIN_VALUEの時にArithmeticException例外がスローされるというところです。

 

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

Java 12で追加されたcondy関連のパッケージですが、このパッケージのクラス群もよっぽどのことがないかぎり使わないと思います。とりあえず、追加されたところだけさらっと流します。

ConstantDescsクラス

ConstantDescsクラスはプリミティブ型などの定数に対するXXXDescを示す定数が定義されているクラスです。そこに、4種類の定数が追加されました。

  • DirectMethodHandleDesc BSM_EXPLICIT_CAST
  • DirectMethodHandleDesc BSM_GET_STATIC_FINAL
  • DynamicConstantDesc<Boolean> FALSE
  • DynamicConstantDesc<Boolean> TRUE

 

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

indy/condy関連のパッケージですが、JEP 371 Hidden Classに関するAPIが追加されています。

ConstantBootstrapsクラス

削除されたAPIのところで、コンストラクタが削除されたと書いたConstantBootstrapsクラスです。

ConstantBootstrapsクラスだけはHidden Classとは関係ないメソッドが追加されています。ConstantDescsクラスで追加された定数に対応するメソッドです。

  • Object explicitCast(MethodHandles.Lookup lookup, String name, Class<?> dstType, Object value)

ようするにキャストをcondyで行う時のブートストラップです。これはMethodHandleクラスを使えば記述できるのですが、それを簡単に書けるようにしただけのメソッドです。

MethodHandles.Lookupクラス

LookupクラスはindyやcondyでメソッドなどをルックアップするためのfindXXXメソッドを定義しています。

他にラムダ式のように動的に生成したクラスを定義するためにdefineClassメソッドなどがあります。このdefineClassメソッドと同様なメソッドがHidden Class関連で追加されています。

  • MethodHandles.Lookup defineHiddenClass(byte[] bytes, boolean initialize, MethodHandles.Lookup.ClassOption... options)
  • Class<?> ensureInitialized(Class<?> targetClass)

defineClassメソッドのHidden Class版がdefineHiddenClassメソッドです。

defineClassメソッドでロードされたクラスは普通のクラスと同様に扱えますが、Hidden Classはその名の通り隠されたクラスなので普通のクラスとはいくつかの違いがあります。

たとえば、匿名クラスと違って名前があるとか、リフレクションでしかアクセスできないなどです。また、初期化は第2引数がtrueの場合にしか行いません。

後から初期化を強制的に行うようにするのが、ensureInitialziedメソッドです。返り値が初期化を行ったClassオブジェクトになります。

とはいうものの、これらのメソッドを使うことはないだろうなぁ...

MethodHandles.Lookup.ClassOption列挙型

LookupクラスのdefineHiddenClassメソッドで使用するenumが新規追加されました。

定義している定数は次の2種類です。

  • NESTMATE
  • STRONG

NESTMATEはネストしたクラスとしてHidden Classを扱うために使います。STRONGはクラスローダーと強い関係を持たせる場合です。強い関係というのは普通のクラスと同様にクラスローダーがGCされた時に、Hidden Classもアンロードされるということらしいです。

 

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

java.lang.reflectパッケージはリフレクションに関する情報を扱うために使用します。

AnnotatedTypeインタフェース

AnnotatedTypeインタフェースはアノテーションで修飾された型の情報を扱うためのインタフェースですが、メソッドが3つ追加されました。

  • <T extedns Annotation> T getAnnotation(Class<T> annotationClass)
  • Annotation[] getAnnotations()
  • Annotation[] getDeclaredAnnotation()

これらのメソッドはデフォルトメソッドではなくて、普通のメソッドです。そんなことしたら、実装クラスが... と思うかもしれませんが、まったく問題ありません。というのも、これらのメソッドはAnnotatedTypeインタフェースの親インタフェースのAnnotatedElementインタフェースで定義されているメソッドだからです。

それを、なぜ今さらAnnotatedTypeインタフェースに定義しなおしたんですかねぇ。よくわかりません 🤔

 

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

java.langパッケージのサブパッケージは普通の開発者は、まず触れないパッケージでしたけど、ここからは使うかもしれないパッケージです。

CharBufferクラス

Bufferクラスのサブクラスはいろいろありますが、その中のCharBufferクラスだけメソッドが追加されました。

  • boolean isEmpty()

StringクラスのisEmptyメソッドはlengthが0かどうかをチェックしますが、CharBufferクラスはちょっと違います。remainingが0かどうかをチェックします。

Bufferクラスのremainingはpositionとlimitの間にある要素数なので、読み込みを行った後、残りが空かどうかを調べるという使い方になります。

jshell> import java.nio.*

jshell> var buffer = CharBuffer.allocate(10)
buffer ==>

jshell> buffer.isEmpty()
$3 ==> false

jshell> buffer.append("ABC")
$4 ==>

jshell> buffer.isEmpty()
$5 ==> false

jshell> buffer.position(0)
$6 ==> ABC

jshell> buffer.isEmpty()
$7 ==> false

jshell> buffer.position(10)
$8 ==>

jshell> buffer.isEmpty()
$9 ==> true 

jshell>

CharBufferオブジェクトをアロケートしただけで、中身は空だとしても$3のようにisEmptyメソッドはfalseを返します。この時、positionは0、limitは10だからです。

結局、最後に示したようにpositionを10に進めると、isEmptyメソッドがtrueを返します。ほんとに文字があるかどうかではないので、注意が必要です。

 

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

NIOのチャネルもメソッドが追加されています。

SocketChannelクラス

ソケットチャネルにオープンのメソッドが追加でオーバーロードされました。

  • static SocketChannel open(ProtocolFamily family)

引数の型のjava.net.ProtocolFamilyインタフェースはプロトコルのファミリーを表すわけですが、このままだとなんだかよくわかりません。実際に使用するのはProtocolFamilyインタフェースを実装したStandardProtocolFamily列挙型です。

このenumではINETとINET6という定数が定義されています。ようするに、IPv4かIPv6かということです。

今までSocketChannelクラスでは、使用されているIPアドレスによってIPv4とIPv6を自動的に使い分けていたのですが、新たにオーバーロードされたopenメソッドはそれを明示的に行うということです。

 

ServerSocketChannelクラス

ServerSocketChannelクラスもopenメソッドがオーバーロードされています。

  • static ServerSocketChannel open(ProtocolFamily family)

返り値の型が違うだけで、SocketChannelクラスと一緒ですね。

 

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

java.nio.channels.spiパッケージはNIOのチャネルのプロバイダを定義しているパッケージですが、ここでもProtocolFamilyインタフェースに関連するメソッドが追加されました。

SelectorProviderクラス

通信を多重化して扱うSelectableChannelクラスのプロバイダがSelectorProviderクラスです。追加されたのは、SocketChannel/ServerSocketChannelクラスで追加されたopenメソッドと同様のメソッドです。

  • SocketChannel openSocketChannel(ProtocolFamily family)
  • ServerSocketChannel openServerSocketChannel(ProtocolFamily family)

 

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

java.utilパッケージに何が追加されたのかと思うかもしれませんが、肩透かしですw

NoSuchElementException例外

なんで今さら、コンストラクタが追加されたのかとても謎なのですが...

  • NoSuchElementException(String s, Throwable cause)
  • NoSuchElementException(Throwable cause)

 

java.compiler/javax.lang.modelパッケージ

毎度毎度のjavax.lang.modelパッケージです。

SourceVersion列挙型

Java 15に対応する定数が追加されました。

  • RELEASE_15

 

java.compiler/javax.lang.model.elementパッケージ

java.compilerモジュールなので、普通の開発者はまず使わないとは思いますが...

Elementインタフェース

javax.lang.reflectパッケージのAnnotatedTypeインタフェースが変更されたのと同じく、Elementインタフェースもアノテーション関連のメソッドが追加されました。

  • <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationType)

 

Modifier列挙型

クラスなどを修飾する要素を定数として定義するModifier列挙型に、JEP 360 Sealed Classesに関する定数が2つ追加されました。

  • NON_SEALED
  • SEALED

 

TypeElementインタフェース

TypeElementインタフェースはクラスもしくはインタフェースに関する要素を取得するために使用するインタフェースです。ここでも、JEP 360 Sealed Classesに関連してSealed Classで派生することが許されているクラスの一覧を取得するメソッドが追加されています。

  • List<? extends TypeMirror> getPermittedSubclasses()

 

ここで示した機能追加以外にセキュリティ関連でメソッドが追加されていることと、Project PanamaのForeign-Memory Access APIが追加されています。

Forein-Memory Access APIはSecond Incubatorで、何も問題がなければJava 16で正式なAPIになると思います。Forein-Memory Accessもそうですが、Forein-Function Interface関連はいろいろとおもしろいので、機会があれば取り上げてみようと思います。

それにしても、Java 15で追加された機能のうち、普通の開発者が使いそうなAPIはSealed Class関連とjava.nio.CharBufferクラスのメソッド追加ぐらいなのがちょっと寂しいですね。

2020/03/17

JEPでは語れないJava SE 14

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

Javaのアップデートでしか更新しないようになってしまいましたが、半年ぶりのblog更新ですw

ということで、アメリカ西海岸時間の3月17日にJava SE 14がリリースされます。

今回もJEPに含まれていないAPIの変更をメインにJava 14についてまとめていきます。

とはいうものの、Java 14は16もJEPがあります。特にProject Amberがらみの文法変更や、ZGCがWindowsとOS Xに対応したことなどが注目されてますね。

そして、久しぶりにIncubator ModuleがJEPで提案されました!!

Project Panamaで策定されているForeign Funtion Interfaceに関連するモジュールで、ヒープではないメモリへのアクセスを行うためのAPIです。

JEPに関する変更はまた別の機会に書くとして、本エントリーはJEP以外のAPIの変更についてです。

今回もABC順にならんでいます。同じように、セキュリティ系のAPIはちゃんと理解していないので、省略してます。

 

廃止になったAPI

Java 14ではPack200関連の3つのクラスが廃止されました。

これらは、Java 11で@DeprecatedのforRemovalがtrueになったクラスです。

  • java.util.jar.Pack200
  • java.util.jar.Pack200.Packer
  • java.util.jar.Pack200.Unpacker

クラス3つといっても、実際にはPack200クラスの内部クラスも含んでいます。Pack200はもう役目を終えましたね。

もう1つ、セキュリティのACL (Access Control List)関連のパッケージが廃止されました。

こちらは、Java 10でforRemovalがtrueになったものです。

  • java.security.acl

java.security.aclパッケージに含まれていたインタフェース/例外は以下の通り。

  • java.security.acl.Acl
  • java.security.acl.AclEntry
  • java.security.acl.Group
  • java.security.acl.Owner
  • java.security.acl.Permission
  • java.security.acl.AclNotFoundException
  • java.security.acl.LastOwnerException
  • java.security.acl.NotOwnerException

これらのAPIはjava.security.Policyで置き換えができるはずです。

 

廃止予定のAPI

Java 14で追加された廃止予定APIは以下の通り。

メソッド

  • java.lang.Thread.resume
  • java.lang.Thread.suspend
  • java.lang.ThreadGroup.allowThreadSuspension
  • java.lang.ThreadGroup.resume
  • java.lang.ThreadGroup.suspend

コンストラクタ

  • java.lang.invoke.ConstantBootstraps
  • java.lang.reflect.Modifier
  • javax.tools.ToolProvider 

ThreadクラスとThreadGroupクラスのresumeメソッド、suspendメソッドは使うのははばかられていたので、廃止されてくれた方が分かりやすくていいですね。

ConstantBootstrapsクラスはCONDYのブートストラップのためのユーティリティクラスですが、もともとstaticなクラスメソッドしか定義されていないので、コンストラクタがある方がおかしかったわけです。

Modifierクラスもモディファイアの定数と、クラスメソッドのisXXXメソッド群を定義しているクラスなので、コンストラクタは必要ありません。基本的にはClass.getModifiersメソッドやMember.getModifiersメソッドで返されるint値を引数にして、isXXXメソッドでチェックするという使い方です。

ToolProviderクラスも同じくクラスメソッドしか定義していないので、コンストラクタがあるのがおかしかったわけです。

 

追加されたAPI

Java 14で追加されたAPIはjava.baseモジュール、java.compilerモジュール、java.xmlモジュールの3つです。とはいうものの、ほとんどがjava.baseモジュールです。

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

java.ioパッケージではメソッドの追加が2、アノテーションの追加が1つありました。

PrintStreamクラス

なぜ今になって追加されたのか全然理解できませんが、2つのメソッドが追加されました。

  • write(byte[] buf)
  • writeBytes(byte[] buf)

いずれも、内部ではthis.write(buf, 0, buf.length)をコールしています。唯一の違いは、writeメソッドがIOException例外をスローするのに対し、writeBytesメソッドは例外をスローしないという点です。

Serialアノテーション

古のJavaではオブジェクトをシリアライズ/デシリアライズする時にSerializableインタフェースを実装する必要がありました。しかし、Serializableインタフェースはマーカーインタフェースでメソッドは定義されていません。もし、シリアライズ/デシリアライズする時に特別な処理が必要な場合はwriteObject/readObjectメソッドなどを定義する必要があります。

@Serialアノテーションはそれを補うため、シリアライズ/デシリアライズに関連したメソッド/定数を修飾するために使われます。

@Serialアノテーションを使用すべきメソッドは下記の5種類です。

  • private void writeObject(java.io.ObjectOutputStream stream) throws IOException
  • private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException
  • private void readObjectNoData() throws ObjectStreamException
  • ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException
  • ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException

定数は次の2種類。

  • private static final ObjectStreamField[] serialPersistentFields
  • private static final long serialVersionUID

Javadocにはここであげたメソッド/定数以外に@Serialアノテーションを使うことはセマンティックエラーだと書いてあるのですが、今のところコンパイル時にチェックすることはやっていないような気が... 今後のバージョンではコンパイル時のチェックに使われるのでしょうか?

 

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

Java 14では新しいRecords型がプレビュー機能で導入されますが、それに伴ってAPIにも変更がありました。

Classクラス

後述しますが、Records型をリフレクションで扱うためのクラスが導入されており、Classクラスはそれに対応したメソッドが追加されています。

  • RecordComponent[] getRecordComponents()
  • boolean isRecord()

Records型とはいっていますが、実際にはちょっと特殊なクラスというだけなので、Classクラスで扱うことができます。isRecordメソッドはクラスがRecords型かどうかをチェックするためのメソッドですね。

もう1つのgetRecordComponentsメソッドはRecords型で定義されたフィールドを取り出すためのメソッドです。名前から分かると思いますが、RecordComponentクラスがRecords型で定義されたフィールドの情報を保持するクラスです。

jshell> record Data(int x, int y) {}
|  次を作成しました: レコード Data

jshell> var clzz = Data.class
clzz ==> class Data

jshell> var comps = clzz.getRecordComponents()
comps ==> RecordComponent[2] { int x, int y }

jshell> Stream.of(comps).forEach(System.out::println)
int x
int y

jshell>

Records型のDataはint xとint yを定義しているので、getRecordComponentsメソッドの返り値は要素が2つの配列になります。

そのまま出力してみると、フィールドの型と名前が表示されました。

RecordComponentクラスに関しては後ほどもうちょっと説明を加えます。

 

Recordクラス

ClassクラスのところでRecords型はちょっと特殊なクラスと言いましたが、Records型のスーパークラスとなるのがRecordクラスです。列挙型のスーパークラスがEnumクラスだというのと同じようなものですね。

たとえば、Records型のDataで試してみます。

C:\sample>cat Data.java
public record Data(int x, int y) {}

C:\sample>javac --enable-preview --release 14 Data.java
注意:Data.javaはプレビュー言語機能を使用します。
注意:詳細は、-Xlint:previewオプションを指定して再コンパイルしてください。

C:\sample>

Records型はまだプレビュー機能なので、コンパイルに--enable-previewオプションが必要です。また、Java 14以前では使えないので、--release 14を指定しておく必要があります。

これでData.classファイルが生成されたので、javapしてみましょう。

C:\sample>C:\sample>javap -p Data
Compiled from "Data.java"
public final class Data extends java.lang.Record {
  private final int x;
  private final int y;
  public Data(int, int);
  public java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public int x();
  public int y();
}

C:\sample>

DataクラスがRecordクラスを派生させたクラスであることが分かります。また、xとyがフィールドとして宣言され、アクセッサ―メソッドも生成されています。

後は、おなじみのコンストラクタ、equalメソッド、hashCodeメソッド、toStringメソッドが作られていることも分かります。

ついでなので、もうちょっと見てみましょう。

C:\sample>javap -p -c Data
Compiled from "Data.java"
public final class Data extends java.lang.Record {
  private final int x;

  private final int y;

  public Data(int, int);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Record."<init>":()V
       4: aload_0
       5: iload_1
       6: putfield      #7                  // Field x:I
       9: aload_0
      10: iload_2
      11: putfield      #13                 // Field y:I
      14: return

  public java.lang.String toString();
    Code:
       0: aload_0
       1: invokedynamic #18,  0             // InvokeDynamic #0:toString:(LData;)Ljava/lang/String;
       6: areturn

  public final int hashCode();
    Code:
       0: aload_0
       1: invokedynamic #22,  0             // InvokeDynamic #0:hashCode:(LData;)I
       6: ireturn

  public final boolean equals(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: invokedynamic #26,  0             // InvokeDynamic #0:equals:(LData;Ljava/lang/Object;)Z
       7: ireturn

  public int x();
    Code:
       0: aload_0
       1: getfield      #16                 // Field x:I
       4: ireturn

  public int y();
    Code:
       0: aload_0
       1: getfield      #17                 // Field y:I
       4: ireturn
}

C:\sample>

バイトコードを眺めていると、コンストラクタとアクセッサ―メソッドはフィールドに対してのアクセスを行っているだけということが分かります。

おもしろいのが、equalsメソッドなどがINDYで実装されていることです。後述しますが、これに関連したクラスが追加されています。

ちなみに、Recordクラスを派生させたクラスを自作することはできません。

jshell> class Data extends Record {}
|  エラー:
|  クラスは直接java.lang.Recordを拡張できません
|  class Data extends Record {}
|  ^--------------------------^

jshell>

 

StrictMathクラス

StrictMathクラスには6つのメソッドが追加されました。

  • static int decrementExact(int a)
  • static long decrementExact(long a)
  • static int incrementExact(int a)
  • static long incrementExact(long a)
  • static int negateExact(int a)
  • static long negateExact(long a)

decrementExtractメソッドは引数の値を1減算するためのメソッド、incrementExactメソッドは1加算、negateExactメソッドが符号の反転を行うメソッドです。もし、int/longをオーバーフローする場合はArithmeticException例外をスローします。

jshell> int x = 0
x ==> 0

jshell> StrictMath.decrementExact(x)
$10 ==> -1

jshell> int y = Integer.MIN_VALUE
y ==> -2147483648

jshell> StrictMath.decrementExact(y)
|  例外java.lang.ArithmeticException: integer overflow
|        at Math.decrementExact (Math.java:1006)
|        at StrictMath.decrementExact (StrictMath.java:879)
|        at (#12:1)

jshell>

内部的にはMathクラスのdecrementExactメソッドをコールしているだけです。逆にいうと、Mathクラスに定義してあるのにStrictMathクラスにはなかったメソッドを追加したという感じでしょうか。

NullPointerException例外

これまでThrowable例外で定義されていたメソッドがオーバーロードされました。

  • String getMessage()

「なぜ今になってgetMessageメソッドが」と思うかもしれません。その理由はJEP 358 Helpful NullPointerExceptionsにあります。試してみれば、今までよりは分かりやすいメッセージになっているはずですよ。

 

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

ElementType列挙型

Records型が導入されたので、ElementType列挙型の定数も追加されています。

  • RECORD_COMPONENT

ElementType列挙型はTargetアノテーションの引数に使用します。Targetアノテーションはアノテーションを自作する時に使い、自作するアノテーションが何を修飾するものなのかを示します。

Records型のコンポーネントを修飾するアノテーションを作るときに、この定数を使うわけですね。

 

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

MethodHandles.Lookupクラス

普通に使う人には、どう考えても使わないクラスだと思いますが...

INDYで動的にコールするメソッドを定義する時に使用されるのがMethodHandleクラスです。そのMethodHandleオブジェクトをルックアップする時に使われるのが、MethodHandles.Lookupクラスです。ラムダ式などで使われています。

このLookupクラスに追加されたのが、以下の2メソッドです。
  • boolean hasFullPrivilegeAccess()
  • Class<?> previousLookupClass()

Lookupオブジェクトにはアクセス権が設定されています。この中で、privateアクセスされるかどうかをチェックするのがhasPrivateAccessメソッドです。

このhasPrivateAccessメソッドはJava 14でDeprecatedになりました(forRemovalは設定されていません)。

その代わりに追加されたのが、hasFullPrivilegeAccessメソッドです。

hasFullPrivilegeAccessメソッドはprivateアクセスとmoduleアクセスされるかどうかをチェックするメソッドです。

一方のhasPrivateAccessメソッドも動作が変更になり、privateアクセスとmoduleアクセスの両方をチェックするメソッドになりました(内部ではhasFullPrivilegeAccessメソッドをコールしています)。

さて、もう1つの方のpreviousLookupClassメソッドですが、Java 14でLookupクラスはクロスモジュールルックアップがサポートされたことに関連しています。

クロスモジュールルックアップは複数のモジュールに分かれている時に使われます。Javadocに記載されている例を見てもらうのが一番分かりやすいでしょうか。

    Lookup lookup = MethodHandles.lookup();   // in class C
    Lookup lookup2 = lookup.in(D.class);
    MethodHandle mh = lookup2.findStatic(E.class, "m", MT);

このlookup2変数が呼び出し元のlookup変数を返すために使われるのがpreviousLookupClassメソッドです。まぁ、そんなもんかと思っていただければいいかなw

 

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

RecordComponentクラス

前述したようにRecords型の導入に伴ってリフレクションでRecords型を扱うためのクラスがRecordComponentクラスです。Records型自身はClassクラスで扱えるので、Records型で定義するコンポーネントにリフレクションでアクセスするためのクラスです。

RecordComponentクラスは、他のリフレクション用のクラスと同様にAnnotatedElementインタフェースの実装クラスです。

定義されているメソッドは以下の通り。

  • Method getAccessor()
  • AnnotatedType getAnnotatedType()
  • <T extends Annotation> T getAnnotation(Class<T> annotationClass)
  • Annotation[] getAnnotations()
  • Annotation[] getDeclaredAnnotations()
  • Class<?> getDeclaringRecord()
  • String getGenericSignature()
  • Type getGenericType()
  • String getName()
  • Class<?> getType()
  • String toString()

アノテーションに関するメソッドはAnnotatedElementインタフェースで定義されているメソッドです。

getNameメソッドやgetTypeメソッドはその名のとおりですね。おもしろいのが、自動生成されるアクセッサ―メソッドを取得するgetAccessorメソッドや、コンポーネントを定義しているRecords型を取得するgetDeclaringRecordメソッドが定義されているところなどですね。

getGenericSignatureメソッドはジェネリクス化されたコンポーネントの場合、そのシグネチャを文字列で返します。ジェネリクス化されていないとnullが返るようです。

getGenericTypeメソッドはコンポーネントの型を返します。ジェネリクスなコンポーネントであれば型パラメータも一緒に取得できます。ジェネリクスでなければgetTypeと同じ結果になります。

jshell> record Data(String text) {}
|  次を作成しました: レコード Data

jshell> var clz = Data.class
clz ==> class Data

jshell> var component = clz.getRecordComponents()[0]
component ==> java.lang.String text

jshell> System.out.println(component.getDeclaringRecord())
class REPL.$JShell$11$Data

jshell> System.out.println(component.getName())
text

jshell> System.out.println(component.getType())
class java.lang.String

jshell> System.out.println(component.getGenericSignature())
null

jshell> System.out.println(component.getGenericType())
class java.lang.String

jshell> record Data2(List<String> texts) {}
|  次を作成しました: レコード Data2

jshell> var clz2 = Data2.class
clz2 ==> class Data2

jshell> var comp2 = clz2.getRecordComponents()[0]
comp2 ==> java.util.List texts

jshell> System.out.println(comp2.getGenericSignature())
Ljava/util/List<Ljava/lang/String;>;

jshell> System.out.println(comp2.getGenericType())
java.util.List<java.lang.String>

jshell> System.out.println(comp2.getType())
interface java.util.List

jshell>

Stringクラスのコンポーネントの場合、getGenericSignatureメソッドはnullを返しています。また、getGenericTypeメソッドとgetTypeメソッドは両方ともjava.lang.Stringを返していることが分かります。

一方、List<String>クラスのコンポーネントだと、getGenericSignatureメソッドがLjava/util/List<Ljava/lang/String;>;になっています。シグネチャの読み方ですが、Lは参照型を表していて、そのクラスがLと;に挟まれた部分になっています。なので、これはList<String>を表しています。

これはgetGenericTypeメソッドで返る値と同じです。

一方でgetTypeメソッドは型パラメータがないListになっていることが分かります。

 

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

なんとパッケージが1つ追加されていました。今のところ定義されているクラスは1つです。

ObjectMethodsクラス

ObjectMethodsクラスが定義しているメソッドは1つ。このメソッドを見ると、このクラスが何のためのクラスか分かりますw

  • static Object bootstrap(MethodHandles.Lookup lookup, String methodName, TypeDescriptor type, Class<?> recordClass, String names, MethodHandle... getters)

bootstrapメソッドといえばINDYです。

Javadocを見ると、Object.equals/hashCode/toStringの3メソッドを生成するとあります。この3つのメソッドというと、先ほどのRecords型で生成されたメソッドがINDYを使っていましたね。どうやら、Records型のクラスを定義すると、このbootstrapメソッドで実行時にこれらのメソッドを生成するようです。ちゃんと追っていないので、もしかしたら違うかもしれませんが 😅

 

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

CompactNumberFormatクラス

CompactNumberFormatクラスにコンストラクタが1つ追加されました。

  • CompactNumberFormat(String decimalPattern, DecimalFormatSymbols symbols, String[] compactPatterns, String pluralRules)

これまでのコンストラクタと比べると、最後のpluralRulesが追加されています。Plural RuleはUnicodeで決められているLanguage Plural Rulesのことです。記述のしかたもUnicodeのドキュメントを見てください。

とはいうものの、CompactNumberFormatオブジェクトを自分でコンストラクタを使って生成することは、ほぼないと思うんですよね。

 

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

LockSupportクラス

ロックのサポートのためのLockSupportクラスですが、スレッドのスケジューリングを行うためカレントスレッドを無効にするparkメソッドがあります。

parkメソッドには引数でブロッカーを指定することもできるのですが、引数なしのオーバーロードもあります。

引数なしのparkメソッドをコールする前に、ブロッカーを設定するためのメソッドが追加されました。

  • statci void setCurrentBlocker(Object blocker)

 

java.compiler/javax.lang.modelパッケージ

SourceVersion列挙型

毎度のことですが、定数が追加されています。

  • RELEASE_14

 

java.compiler/javax.lang.model.elementパッケージ

ElementKind列挙型

パターンマッチングとRecords型に対応するため定数が追加になっています。

  • BINDING_VARIABLE
  • RECORD
  • RECORD_COMPONENT

BINDING_VARIABLEがinstanceofを使ったパターンマッチングに対応しています。

 

RecordComponentElementインタフェース

こちらもRecords型に対応するために追加されたインタフェースです。定義されているメソッドは3つ。

  • ExecutableElement getAccessor()
  • Element getEnclosingElement()
  • Name getSimpleName()

getAccessorメソッドがコンポーネントのアクセッサ―メソッドに対する要素を返します。getEnclosingElementメソッドはコンポーネントを定義しているRecord型の要素を返します。

 

TypeElementインタフェース

こちらもRecords型に対応するためにメソッドが追加されました。

  • List<? extends RecordComponentElement> getRecordComponents()

 

java.compiler/javax.lang.model.utilパッケージ

javax.lang.model.utilパッケージではコンパイル時に使用するプログラム要素の扱うためのビジターなどをいろいろと定義しているパッケージです。こちらもRecords型導入にともなってビジターなどが追加されています。

追加されたクラスだけを列挙しておきます。

  • AbstractElementVisitor14
  • AbstractTypeVisitor14
  • ElementKindVisitor14
  • ElementScanner14
  • SimpleAnnotationValueVisitor14
  • SimpleElementVisitor14
  • SimpleTypeVisitor14
  • TypeKindVisitor14

 

java.xml/org.xml.saxパッケージ

ContentHandlerインタフェース

XMLのSAXパーサーで使用するContentHandlerインタフェースにメソッドが追加されました。。

  • void declaration(String version, String encoding, String standalone) throws SAXException)

逆に、今までなぜdeclarationを受け取るメソッドがなかったのかの方が不思議ですね。

ちなみに、このメソッドはdefaultメソッドで定義されていますが、defaultメソッドでは何も行っていません。

 

ということで、Java 14のAPIの変更をまとめてみました。Records型導入による追加が大きいですね。逆にいうと、それ以外のAPI追加はほとんどないのがちょっと寂しい...