torutkのブログ

ソフトウェア・エンジニアのブログ

Java読書会「基礎からのServlet/JSP第5版」を読む会(第1回)

新しい本「基礎からのServlet/JSP第5版」

今月のJava読書会BOF主催のJava読書会は、「基礎からのサーブレット/JSP 第5版」を読む会(第1回)を開催しました。

最新のServlet 6.0/JSP 3.1に対応し、Tomcat 10.1とOpenJDK 21で動かしながら学んでいく内容です。

概要

前回は洋書でしたが、今月からは和書です。が、参加人数が3名と少ない状況でした。(3連休の合間だったせいもあるか、宣伝不足か、読書会による勉強はニーズが少なくなっているのか、など憶測色々ありました)

この書籍では、メインはWindows上でOpenJDK 21とTomcat 10.1、H2 Database 2.1を使ってServletJSPのプログラミングを学んでいこうという内容です。

Tomcatは、最後に触ったのが20年前か或いはもっと前か、懐かしいというかほとんど覚えていないです。

今時点で書籍サポートサイトには正誤表は記載ありませんが、読書会をすると毎回誤植を見つけるよねと話して読み進めていたら、いくつか発見しました。

読書会の進め方は、例によって、本文、表、図、ソースコードを朗読して進めました。

昼食は、近くの生ハラミ焼肉屋さんで焼肉ランチを食べました。

TomcatとJetty

本書はTomcatを使っていますが、書籍では他のJakarta EEアプリケーションサーバとして、WildFly、Jetty、GlassfishWeblogic Serverが紹介されています。 JettyとTomcatの違いは?と議題になり、Tomcatはサーバープログラムをまずインストールして動かし、そこにサーブレットJSPを使うプログラムを展開(デプロイ)する使い方、JettyはサーブレットJSPを使うプログラムにWebサーバーが含まれる使い方となるということでした。

開発環境構築

書籍は、Windows環境での動かし方を記載し、付録でMacOSLinuxの環境に触れています。書籍は、サンプルプログラムと一緒の作業ディレクトリにOpenJDK、tomcat、H2 Databaseを展開したアーカイブファイルを作り、書籍サイトからダウンロードして展開してねという形です(200MB超過のアーカイブファイル)。

MacOSでの環境(書籍とは別)

MacOSでは、Homebrewにtomcat 10.1が用意されており、brew install tomcat で 10.1.19がインストールされました。OpenJDKもH2 DatabaseもHomebrewでインストール可能です。

Homebrewでインストールしたら、catalina runでサーバーが起動しました。簡単ですね。デプロイは、/opt/homebrew/opt/tomcat/libexec/webapps/ ディレクトリの下にアプリケーションのディレクトリ(本書ではbook)を作成、その下のWEB-INF/classesにパッケージに対応してディレクトリ・クラスファイルを配置します。

Tomcat周りのメモ
  • examplesアプリケーションは、tomcatをインストールしたマシンからのみアクセス可能。META-INFのcontext.xmlに、リモートアクセスの許可が 127...*、::1、0:0:0:0:0:0:0:1に限定されているため
  • ROOT にTomcatのようこそ画面を表示するアプリケーションがある
  • Java Platform Module Systemに対応しているのだろうか?

Java関連書籍「ソフトウェア設計のトレードオフと誤り」

正月休みの時にぶらっと本屋さんに寄った時に、Java読書会BOFの次の読書会の候補本になりそうかと買ってみました。

題名 ソフトウェア設計のトレードオフと誤り
著者 Tomasz Lelek, Jon Skeet
訳者 渋川よしき ら訳
出版 オライリー・ジャパン 2023年5月刊行
ISBN 978-4-8144-0031-7
価格 4180円(税込)

プログラム、アプリケーション、システムを作成するときに遭遇する設計上の選択を、トレードオフとして紹介しています。課題は、プログラミング・コード上のもの(例:シングルトン・パターンの実装方法、継承とコンポジションの使い分け、例外)から、共通ライブラリ、バージョニング、APIの柔軟性と複雑性、最適化、日付と時間、データのローカリティ、メモリかディスクか、サードパーティライブラリの利用、分散システムの一貫性、アトミック、配信、DIフレームワーク、リアクティブプログラミング、関数プログラミングの使いどころなどが並んでいます。 コードはJavaで書かれています。

読み始めたばかりですが、最初の方に出てくるシングルトン・パターンでは、同期をgetInstanceでかける方法、ダブルチェックロッキング、スレッドローカル変数での実現(厳密にはシングルトンではなくなりますが)を列挙し、パフォーマンスを含めてトレードオフしています。

日付と時間についてかなり深い記述がありますが、著者の一人JonがNoda Time(.NET用の日時ライブラリ)の開発者なのでうなづけます。 なお、Noda Timeのライブラリ名から推測できる通り、JavaのJoda Timeライブラリの.NETポーティングです。Joda Timeは、javaのdate & time APIの元になったライブラリです。

IntelliJ IDEA 2023.3 で Java 21 Preview機能を使う

IntelliJ IDEA 2023.3 と Java 21

先日リリースされた、IntelliJ IDEA 2023.3では、Java 21対応が完全サポートと謳われています。

9月に書いたブログ(下記)では、IntelliJ IDEA 2023.2.1でOpenJDK 21のPreview機能であるJEP 445 Unnamed Classes and Instance Main Methods の構文を認識できなかったと書きました。その後も2023.2.5までは未対応でした。

今回、2023.3になってどうなったかを見てみます。

torutk.hatenablog.jp

IntelliJ IDEA 2023.3 で JEP 445

New Projectを作成

IntelliJ IDEAの[File]メニュー > [New] > [Project...] を選択し、「New Project」ダイアログを表示します(下図)。

  • ① プロジェクト名を入力、この名前がディレクトリ名、生成されるJARファイルの基底名などに使われます。
  • ② プロジェクトディレクトリを作成する親ディレクトリを指定します。
  • ③ このプロジェクトで開発対象とするプログラミング言語を指定します。
  • ④ このプロジェクトのビルド・実行・デバッグ・配布などの活動に使うビルドツールを指定します。
  • ⑤ このプロジェクトのビルド・実行に使うJDKを指定します。
  • ⑥ ビルドツールに④でGradleを指定したとき、Gradleのビルド定義ファイルの記述に使うDSLの種類を指定します。
  • ⑦ 追加設定をするために、Advanced Settingsの先頭の[>]をクリックし追加設定を表示します。
  • ⑧ このプロジェクトが使うビルドツールGradleの共有方法を指定します。通常はWrapperを使用します。
  • ⑨ このプロジェクトで生成するアーティファクト(成果物)のGroupIdを指定します。成果物のオーナーを識別する目的で使われます。Javaの場合、パッケージ名に使用する組織(オーナー)のドメイン名に基づく部分を抽出して使用することが多いです。その下のArtifactIdは、通常①で指定したプロジェクト名と同じものが入っていますのでそのまま使います。

プロジェクトが生成されました。build.gradle.ktsが表示されます。

mainメソッドを記述

JEP 445では、無名パッケージのjavaソースファイルに、クラス定義なしに直接 メソッドなどを記述できます。 そこで、ソースディレクトリ( src/main/java/)の下に新規Javaソースファイルを作成します。

  • 左側ペインの src > main > java を選択し右クリックで、New > Java Class を選択して「New Java Class」ダイアログを表示
  • 名前にHelloとつける

では、Hello.java ファイルにmainメソッドを記述します。

エラーが出ています。OpenJDK 21では、JEP 445 は Preview機能なのでビルドするにはコンパイルオプションでPreview機能を有効にする必要があります。 IntelliJ IDEAでは、[File]メニュー > [Project Structure]で「Project Structure」ダイアログを表示し、Language level欄で[21 (Preview) ...]を設定します。

実行(IntelliJ IEA上から)

mainメソッドの宣言行にある緑枠の右三角アイコンをクリックすると、mainメソッドを実行します。

しかし、エラーとなってしまいました。先程の、Lanugage levelの設定とは別に、Gradleの定義でPreviewを有効にする必要がありそうです。

  • 試行1 Settings > Build, Execution, Deployment > Compiler > Java Compiler を開き、[Additional command line parameters]に、--enable-preview を設定した。結果、エラーが出るのは変わらず。

  • 試行2 Gradle Documentに記載のタスク定義を追記

次のドキュメントの Enabling Java preview features の記載の定義を、build.gradle.ktsに追記します。

Building Java & JVM projects

tasks.withType<JavaCompile>().configureEach {
    options.compilerArgs.add("--enable-preview")
}

tasks.withType<Test>().configureEach {
    jvmArgs("--enable-preview")
}

tasks.withType<JavaExec>().configureEach {
    jvmArgs("--enable-preview")
}

この定義を追加することにより、コンパイル・実行が可能となりました。 IntelliJ IDEA上から実行するときは、Runメニュー、またはmainメソッドの宣言行の三角アイコンから可能です。

実行(コマンドライン

コマンドラインから実行する場合、この時点の記述ではGradleのタスクで実行は用意されていないので、javaコマンドにパスを指定して実行する必要があります。

hello % ./gradlew build    

> Task :compileJava
  :
BUILD SUCCESSFUL in 927ms
2 actionable tasks: 2 executed
hello % ls build/libs 
hello-1.0-SNAPSHOT.jar
hello % java --enable-preview -cp build/libs/hello-1.0-SNAPSHOT.jar Hello
Hello, Java 21 Preview world.
hello % 

まとめ

IntelliJ IDEAでGradleをビルドツールに使うプロジェクトで OpenJDK のプレビュー機能を使ってプログラミングする場合、

  • Project Structureで、Language level に Previewを有効にするバージョンを指定する
  • Gradleのビルド定義(build.gradle.kts)に、コンパイル・実行時に javaVMオプション --enable-previewを指定する記述を追記する

を行います。

Java読書会「Practical Design Patterns for Java Developers」(第4回)を終えて

11月18日(土)に、Java読書会BOF主催のJava読書会「Practical Design Patterns for Java Developers」を読む会(第4回)を開催しました。

本日は、会場確保が出来なかったので、オンライン(Skype)で開催しました。

オンライン読書会の手段

Java読書会BOFでは、コロナ禍の際にオンライン開催を幾度か実施しました。そのときは、MicrosoftのTeams(無料アカウント枠)を利用しました。Java読書会は通常10:00から開始し、17:00までの7時間、途中で随時休憩を挟みながら実施しています。オンラインでのWeb会議では、人数制限と時間制限があるものが多く、当時は無料枠では TeamsがJava読書会の開催に合致していました。

さて、今回オンライン開催とする際に、Teamsは無料版では会議の時間が最大60分と制限されるようになっており、検討の結果 Skypeを使うこととしました。Skypeは特に時間の制限がないようで、画面共有もできました。

読書会メモ

今回も、「Practical Design Patterns for Java Developers」(洋書)の電子版(PDF)をGoogle翻訳したものをつかって朗読で進めました。電子版(PDF)は、紙の書籍を購入した場合、出版社にエビデンス(領収証)を送ると 

ソースコードの部分は翻訳が入ると読みにくいので英語のPDFで朗読しました。第4章「構造に関するデザインパターン」(p.101から)と第5章「振る舞いに関するデザインパターン」の途中、インタープリタパターン(p.150まで)を読みました。

UML図について

各パターンの説明において、UMLクラス図が示されていますが、図の誤記が多く見かけられました。

インタフェースを実装する関連線が、◁- - - - (点線)ではなく、◁-----(実線)となっていた。

UML 2.0のクラス図では、インタフェースはインスタンス化できない抽象型で、それを実装するクラスとの間は、三角記号と点線(InterfaceRealization)で結ぶのですが、この書籍では多くが実線(Generalization)で結ばれています。

ダウンキャストのswitchパターンマッチ

Adapterパターンのサンプルコードに次がありました。

sealed interface Engine permits ElectricEngine, PetrolEngine {...}
class ElectricEngine implements Engine {...}
class PetrolEngine implements Engine {...}

class Vehicle {
    private final Engine engine;  
      :
    public void refuel() {
        switch (engine){
        case ElectricEngine de -> {
          :
        }
        case PetrolEngine pe -> {
          :
        }
        default -> throw new IllegalStateException("Vehicle has no engine");

JDK 21で正式機能となったJEP 441 Pattern matching for switchが使われています。 switchで、Engine型のengineを渡すと、caseで、具象型に基づく選択ができます。なるほど、これはキャストやinstanceofがまったく登場せずにダウンキャストの選択ができています。

さらに、sealedでEngineインタフェースを実装する型をElectricalEngineとPetrolEngineに限定しています。すると、上述コードのdefaultはいらないのでは? と削除してみてもコンパイル通りました。

その他メモ
  • java.util.Propertiesクラスは、JDK 1.0からのAPIで、Hashtableを継承しています。互換性のため変更できませんが、実装ではConcurrentHashMapを内部でつかって、プロパティはこれにput/getしています。

  • コマンドパターンのところで、サンプルコードではrecord型を使っていました。 ここで、enumを使ってもいいのではとの議論をしました。enumでは、列挙子毎に異なるメソッドの実装を記述できます。

  • JDK 21までの新しい機能についての書籍がなかなか出ていませんが、この本は何気にJDK 21の機能まで(プレビューで少し前のJDKバージョンから試せていますが)使っているので、デザインパターンだけでなくJavaの新機能の知識も付きますね。(第1回、第2回はデザインパターンの章にたどり着けず、Javaの深い機能、新機能で終始していました)

Java読書会「Practical Design Patterns for Java Developers」(第3回)

Java読書会「Practical Design Patterns for Java Developers」(第3回)の予習

10月22日(日)は、Java読書会BOF 主催のJava読書会「Practical Design Patterns for Java Developers」を読む会(第3回)が開催されます。

今回は、第3章 生成に関するデザインパターン(Working with Creational Design Patterns)から読み始めます。 本書では生成に関するデザインパターンとして次が解説されています。

  • ファクトリメソッド・パターン
  • アブストラクトファクトリー・パターン
  • ビルダー・パターン
  • プロトタイプ・パターン
  • シングルトン・パターン
  • オブジェクトプール・パターン
  • 遅延初期化(lazy initialization)・パターン
  • 依存性注入(dependency injection・パターン

半数強が古典 GoF本に記載のものです。実装例としてJavaの新機能の利用としては、

  • switch式
  • record型
  • テキストブロック(パターンの実装ではなく、エラー時やprintlnでの文字列生成に使用)

シングルトンでは、マルチスレッド時の対策としてenumを使った実装例も紹介していました。

第3章が38ページなので、第4章 構造に関するデザインパターン(Applying Structural Design Patterns)も読み進める予定です。ここはボリュームが多いので途中までとなりますが、Java読書会の1回あたりの平均ページ数60ページから、第4章の最初の20ページほどに書かれてる次のパターンが今回の対象になると想定します。

  • アダプター・パターン
  • ブリッジ・パターン
  • コンポジット・パターン
  • デコレーター・パターン
  • ファサード・パターン
  • フィルター・パターン
  • フライウェイト・パターン

ざっと目を通した限り、前章と同じくswitch式、record型が登場していた他は、最新Java機能は登場していないようです。 ラムダ式を使った実装が見かけましたが、ラムダ式Java SE 8で導入されたので今更新しい機能とは言えないですね。

次回の読書会も、機械翻訳で読み進める予定なので、英語の敷居はほとんどないかと思います。 前回までは、JVMのディープなところ、Javaの新しい機能の解説で読むのが大変でしたが、今回は図とコード例も多く、比較的順調に進むと思います。

Java読書会「Practical Design Patterns for Java Developers」(第2回)を読む会を終えて

先月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

Java SE 17 API document - java.util.Random すべてのメソッド(抜粋)

Java SE 17 API Document - java.util.Random 実装するインタフェースで宣言されたメソッド

module-info.javaはjavacでコンパイル時は明示的に指定必要?

書籍では、module-info.javaを伴うコンパイルをjavacで実施するときに、javacのコマンドラインで明示的にmodule-info.javaを指定していました。

javacの-sourcepathオプションを指定しないと、javacに渡したソースファイルのみがコンパイルされます。 javacの-sourcepathオプションを指定すると、javacに渡したソースファイルに加えてmodule-info.javaコンパイルされます。

javacで-sourcepathを指定したときの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) と続き、デザインパターンの準備が終わりました。

やっと次回から、デザインパターンの章に入ります。

Java読書会「Practical Design Patterns for Java Developers」(第2回)を読む会

今週末の9月23日(土)は、Java読書会BOF主催の「Practical Design Patterns for Java Developers」を読む会(第2回)を開催します。

洋書ですが、電子版(PDF)を機械翻訳したものをベースに日本語で朗読していくので、英語の敷居はほぼ低いです。 (double sin = Mathsin(...)が、二重の罪 = と訳されるなど楽しい翻訳を目にすることもできます)

今回の範囲は、日本語であれば、Java読書会のこれまでの平均60ページの進度で読み進めるとの想定で、次の項目となります。

第2回の予定範囲:p.37 l6から p.97まで

  • 第2章 

    • pp.37-39 GC マイナーGCとメジャーGC、マーク、コンパクト、ソフト参照、ファントムリファレンス
    • pp.40-49 デザインパターンに使用する最も重要基本API、プリミティブ型とラッパー、文字列APIの操作、配列、コレクションフレームワークとOノーテーション、Math API、ラムダと関数型インタフェース
    • pp.50-52 Javaモジュールシステム(JPMS)
    • pp.52-57 Java 11から17以降の機能簡単レビュー ラムダパラメータのローカル変数構文、スイッチ式、テキストブロック、instanceofパターンマッチング、レコード、シールクラス、デフォルトUTF8、スイッチのパターンマッチング
    • pp.57-60 Javaの同時実行性、Executorサービス、タスクの実行、Future
  • 第3章生成に関するデザインパターン

    • pp.65-71 ファクトリメソッドパターン、ファクトリクラス、switch式で簡素化、レコードクラス
    • pp.71-75 抽象ファクトリパターン、FactoryProvider
    • pp.75-78 ビルダーパターンで複雑なオブジェクトのインスタンス化 2つのアプローチ
    • pp.78-82 プロトタイプパターンでオブジェクトのクローン作成
    • pp.82-85 シングルトンパターン、オンデマンドで遅延作成
    • pp.85-93 オブジェクトプールパターン

Design Patternの本ですが、ご覧の通り、今回の範囲では、前半はJavaのメモリ管理の話、続いてJavaAPIJavaモジュールシステム、Java 11以降の新しい言語仕様、スレッドの話となります。

第1回では、後半、突如としてJMMJava Memory Model)の話が出て、まずJDKの構成、次にJVMの話でクラスロード、リンク、実行を解説、続いてメモリの構成(スタック、ヒープ、メソッド領域、プログラムカウンター、ネイティブスタック)、そしてメモリモデル(キャッシュと競合、volatile)、 GCの解説となりました。

これらを踏まえてDesign Patternを解説するぞ、という意気込みで、なかなか読み応えのある読書向きの本ではないかと感じています。

Java読書会BOFでは、参加者募集中です。