先月9月23日(土)に、Java読書会BOF主催の「Practical Design Patterns for Java Developers」を読む会(第2回)を実施しました。 今回は、進みが遅く、通常の半分ほどでした。今回の範囲は、デザインパターンの説明に入るための準備として、コアAPIの解説、Java 11から最近のバージョンまでに追加された新機能を駆け足で紹介しているのですが、それを一つ一つ理解し読み進めていくのに時間がかかってしまいました。
読書メモ
参照の4種類
Javaの参照には4つの種類(Strong references, Weak references, Soft references, Phantom references)あり、WeakとSoftの違いは、WeakがGCへのヒントとなりGCの回収対象となるが、Softはメモリ不足が発生した時に、アプリケーションがOutOfMemoryErrorを出す前に回収されるとの説明があり、なるほどと思いました。
unsignedの扱い
プリミティブ型とラッパー型において、unsignedで少し横道に外れました。 Javaは基本的にはunsignedな数値の型はありませんが、バイナリデータを扱うときに少しだけunsignedをサポートをするメソッドがJDK 8で追加されています。 Integer.compareUnsigned(int x, int y)で、引数の整数を符号無しとして比較、Integer.divideUnsigned(int dd, int ds)で符号無しの除算、や、Integer.toUnsignedLong(int x)、そして文字列との変換をするInteger.toUnsignedString(int i)やInteger.parseUnsignedInt(String s)などです。この追加機能に言及している書籍を探してみたところ、次の書籍に半ページほど記載がありました(8.2 数値クラス)。
Javaプログラマーなら習得しておきたい Java SE 8 実践プログラミング - インプレスブックス
コレクションフレームワーク、Java SE 21での変更
コレクションフレームワークの説明で、Collection(インタフェース)を継承するList、Queue、Setのインタフェースと、Collectionを継承しないMapがあるとの説明がありました。 ちょうど先週リリースされた Java SE 21のJDKから、コレクションフレームワークに変更が入り、Collectionを継承するSequencedCollectionが追加され、ListはこのSequencedCollectionを継承するようになりました。
java.util.RandomクラスのnextDouble(double)はどこで定義?
java.util.RandomのAPIドキュメントを見ると、nextDouble(double)が見つからないのですが? 次のJava SE 17のAPIドキュメントで、java.util.Randomクラスのメソッドを見ると、nextDouble()はあるがnextDoune(double)がすべてのメソッドの一覧に見つかりません。 Random (Java SE 17 & JDK 17)
よくよく見ていくと、java.util.Randomクラスは、インタフェースjava.util.random.RandomGeneratorを実装しています。このRandomGeneratorインタフェースには、nextDouble(double)がデフォルト実装で定義されています。 そのため、java.util.Randomインスタンスに対して nextDouble(double)メソッドの呼び出しが可能でした。 Java SE APIドキュメントでは、そのクラスがimplementsしているインタフェースのdefaultメソッドについては、引数・戻り値がないメソッド名のみが下の方に記載されているのみです。
https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/Random.html
module-info.javaはjavacでコンパイル時は明示的に指定必要?
書籍では、module-info.javaを伴うコンパイルをjavacで実施するときに、javacのコマンドラインで明示的にmodule-info.javaを指定していました。
javacの-sourcepathオプションを指定しないと、javacに渡したソースファイルのみがコンパイルされます。 javacの-sourcepathオプションを指定すると、javacに渡したソースファイルに加えてmodule-info.javaもコンパイルされます。
record型のクラスファイルにはどのようなバイトコードが?
次のレコード型のバイトコードを見てみます。
public record HelloRecord(String message) {}
コンパイルされたクラスファイルをjavapにかけてみます。
public final class javareading.HelloRecord extends java.lang.Record : public javareading.HelloRecord(java.lang.String); : public final java.lang.String toString(); : public final int hashCode(); : public final boolean equals(java.lang.Object); : public java.lang.String message(); :
record型は、java.lang.Recordを継承するfinalクラスとして生成されています。 コンストラクタの他、toStringメソッド、hashCodeメソッド、equalsメソッド、messageメソッド(Getterメソッド)が定義されています。
ここで、toStringメソッド、hashCodeメソッド、equalsメソッドの実装はinvokedynamicが使われており、hashCodeの具体的な実装(計算ロジック)はバイトコードでは確認できませんでした。
public final int hashCode(); descriptor: ()I flags: (0x0011) ACC_PUBLIC, ACC_FINAL Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokedynamic #17, 0 // InvokeDynamic #0:hashCode:(Ljavareading/HelloRecord;)I 6: ireturn
sealed class
クラスやインタフェースをsealedにすると、permitsで指定した型でのみ継承(extends)、実装(implements)が可能で、他の型は継承・実装ができなくなります。
public sealed interface Vehicle permits Car, Bus { void start(); void stop(); }
このコード例では、Vehicleインタフェースは、CarおよびBusクラスでのみ実装可能です。 CarとBusは、Vehicleのコンパイル時に存在している必要がありました。 なかなかユースケースが微妙ですが、APIとしてinterfaceを公開するときに、API利用者がその公開されたinterfaceのメソッドを呼び出すだけにとどまらず、interfaceを利用者側でimplementsしてしまっていると、後日の変更に支障が出るのでimplementsはさせたくないといったところが一例でしょうか。
JDKのクラス群でどれだけsealedが使われているのかを、JDKのsrc.zipから正規表現でざっと抜き出してみたところ、400箇所弱ほどありました。パッケージとしては、sun.security、jdk.internal、java.util, java.lang.ref, java.lang.invoke, java.lang.constant, java.nio, com.sun.crypto, java.awt, javax.swing といったところでした。
その他
switchのパターンマッチング、デフォルトエンコーディングがUTF-8、 スレッド(Threadクラス、Executors、Future) と続き、デザインパターンの準備が終わりました。
やっと次回から、デザインパターンの章に入ります。