torutkのブログ

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

Java読書会「基礎からのサーブレット/JSP 第5版」を読む会(第3回)

Java読書会BOF 主催の「基礎からのサーブレット/JSP 第5版」を読む会(第3回)を4月27日(土)に開催しました。

今回は、読書会で読んだ範囲からいくつかトピックを記載します。

第3回を開催して、Chromeの振る舞いを知る

サーブレットが、シングルインスタンスでマルチスレッドでアクセスされる例において、

p.161

1つのブラウザ、たとえばChromeで同じサーブレットに対して2枚のウィンドウ(またはタブ)を開いた場合には、片方のウィンドウの処理が終了するまで、もう片方の処理は待機するので、カウンタの値が不適切になりません。

との記述がありました。試してみたところ、ウィンドウを2枚開いて、同じサーブレットにアクセスしてみましたが、ほぼ同時にアクセスでき、カウンタの値が不適切になりました。(ここで不適切とは、サーブレットクラスのフィールドにカウンタを保持し、アクセス時にフィールドの値を取得し数秒ウェイトしてから値をインクリメントしてフィールドに代入するコードのため、マルチスレッドでは複数が同じ値を取得することがあるという意味)

試したブラウザは、SafariFirefoxでしたが、Chromeを使ったところ不適切な更新は発生せず、2枚目のウィンドウがサーブレットにアクセスするのが遅い動きをしていました。 Edgeでも不適切な更新が発生せず、どうやらChromiumエンジンが関与しているようです。

サーブレットのURLに、リクエストパラメータを追加し、その値を2枚のウィンドウで変えてみたところ、不適切な更新がChromeでも発生しました。このことから、同一のURLに対するアクセスは、先にアクセスした方が完了するまで次のアクセスが待たされているようでした。

デベロッパーツールで調べてみると、2つ目のウィンドウからのアクセスは数秒間Stalledとなっていました。ぐぐってみたところ、Chromeは同一URLへのアクセスが並行して発生した場合、キャッシュコントロール上1つ目のリクエストが復帰するまで2つ目のリクエストの送出を待機する仕組みがあるとのことです。 なので、クエリパラメータを付加してその値をかえることでこの仕組みを回避することができていたのでした。

フィルターがCSSなどのファイルの読み込みを阻害

サーブレットフィルタの解説で、次のようにServletResponseのsetContentTypeを各サーブレットに記述するのは冗長なため、フィルタで記述する例がありました。

@WebFilter(urlPatterns={"/*"})
public class EncodingFilter implements Filter {
    public void doFilter(...) {
        :
        response.setContentType("text/html; charset=UTF-8");

このサンプル(フィルタ)を使用していた環境で、HTMLファイル(hello.html)にcssファイルを適用してみたところ、cssが反映されないという事態が生じました。

ブラウザのデベロッパーツールで調査したところ、次のエラーが発生していました。

[Error] Did not parse stylesheet at 'http://localhost:8080/book/css/book.css' because non CSS MIME types are not allowed in strict mode.

フィルタのURLパターンが /* のため、CSSファイルもフィルタのURLパターンにふくまれてしまい、cssファイルが本来 text/css というMIMEタイプで送信されるべきところ、text/htmlとなってしまったためです。

対応策としては、次が思い浮かびます。

  • サーブレットJSPのURLを、CSSや画像ファイルなどのリソースとは別な要素、例えば/servlet/xxx のように割り振り、フィルタのURLパターンを /servlet/* のように指定する
  • サーブレットのURLを、xxx.servletのように拡張子パターンが適用できるように命名し、フィルタのURLパターンを、.servlet.jsp のように指定する

URLパターンの指定では、ワイルドカードの使用は /パス要素/* のようにスラッシュで区切った後ろに指定することはできますが、/パス要素の途中* のように文字列の途中から後をワイルドカード指定することができません。

拡張子を指定する場合、パス要素を指定することができません。

リソースのクローズ

データベースへのアクセスで、サーブレットからJNDIでデータソースを取得し、JDBCでデータベースへアクセスします。

この際に、JNDIの InitalContext、JDBCのConnection、PreparedStatement、ResultSetを取得しています。これらは使用が終わったらcloseを読んでリソースを解放する必要があります。

書籍のサンプルでは、次のようなコードとなっています。

public void doGet(...) {
    :
    try {
         InitialContext ic = new InitialContext();
        DataSource ds = (DataSource) ic.lookup("java:/comp/env/jdbc/book");
        Connection con = ds.getConnection();
        PreparedStatement st = con.prepareStatement("select * from product");
        ResultSet rs =st.executeQuery();
        while (rs.next()) {
           :
        }
        st.close();
        con.close();
    } catch (Exception e) {
        e.printStackTrace(out);
    }
    :
}

このコーディングでの問題点は次です。

  • tryブロック内の処理途中で例外が発生した場合、たとえば executeQueryの実行中に例外が発生すると、その後のcloseを呼び出すことなく catch節に処理が移行してしまうため、リソースリークが生じる
  • ResultSet、InitialContextに対するcloseの呼び出しがない
    • JDBCAPI規定上は、Statementのcloseを呼び出すと、そのStatementが生成するResultSetもcloseされるとありますが、JDBCの実装が必ずそうなっているとは限らない
    • JNDIのInitialContextは、closeしない場合のリーク発生有無が明示されていませんが、必要と想定

是正案としては次があります。

  • tryブロックではなく、finallyブロックでcloseを呼び出す
  • try-with-resource構文を用いる
try-finallyで実装する案

従来のtry catchで確実にクローズするように finallyブロックでcloseを呼び出す

InitialContext ic = null;
Connection con = null;
Statement st = null;
ResultSet rs = null;
try {
   :
} catch (Exception e) {
   :
} finally {
    try {
        rs.close();
    } catch (Exception e) {
    }
    try {
        st.close();
    } catch (Exception e) {
    }
    try {
        con.close();
    } catch (Exception e) {
    }
    try {
        ic.close();
    } catch (Exception e) {
    }
}    

finallyブロックで、それぞれのリソースにたいしてcloseを呼びます。 それぞれのclose呼び出しをtry-catchで個別に囲っているのは次の理由です。

  • closeメソッドも例外をスローする可能性があるため、例外をスローしたとしても後続のcloseをきちっと呼び出すように制御フローを組んでいる
  • 元のtryブロックで例外が発生し、tryブロックから外へ例外をスローする場合、finallyブロックの中でcloseが例外をスローし、その例外をfinallyブロックから外に出すと、tryブロックの例外がfinallyブロックの例外に上書きされてしまうため

try-catch-finallyで、finallyブロックの中でクローズ対象のインスタンスを参照するには、インスタンス変数が try-catch-finallyの外側で宣言されている必要があります。 その場合、宣言時に実体が存在しないので初期値としてnullを入れています。finallyブロックでは、クローズ対象のインスタンスがnullである場合が想定されるので、上述のようにcloseをtryブロックで実行し、catchでNullPointerExceptionを含めて捕捉するか、次のように nullチェックをしてからcloseを呼び出します。

  • nullチェックをしてからcloseを呼び出し、closeのシグネチャで宣言される例外をcatchする
} finally {
    if (rs != null) {
        try {
            rs.close();
        } catch (SQLException e) {
        }
    }
    :

close()を覆うtry文で、SQLExceptionをcatchしても、何かできることは特にないので、これをcatch (Exception e) とすれば、NullPointerExceptionも捕捉できるので、nullチェックをしなくてもよいかなと思います。  

try-witch-resourceで実装する案

try-with-resourceを使うと大分改善できます。

  • close呼び出しの記述が不要(finallyブロックの記述を省略)
  • 複数のリソースを使用しているときに、1つのcloseで例外が発生しても、残りのリソースに対してcloseが呼ばれる
  • tryブロックで発生した例外を tryブロックの外にスローする場合、closeで例外が発生しても上書きすることはない

ですが、クローズ対象のクラスがAutoCloseableをimplementsしている必要があります。JDBCの例では、JNDIのInitialContextがAutoClosableでないのでやっかいです。

    InitialContext ic = null;
    DataSource ds = null;
    try {
       ic = new InitialContext();
       ds = (DataSource) ic.lookup("java:comp/env/jdbc/book");
    } catch (Exception e) {
        // エラー中断の処理
    }
    try (
        Connection con = ds.getConnection();
        PreparedStatement st = con.prepareStatement("select * from product");
        ResultSet rs = st.executeQuery();
    ) {
        // 
    } catch (Exception e) {
        // エラー中断の処理
    }

PreparedStatementにパラメータをセットする場合、try () の中に記述できないのでもう少し複雑なコードになります。

    try {
        InitialContext ic = new InitialContext();
        DataSource ds = (DataSource) ic.lookup("java:comp/env/jdbc/book");
        try (
            Connection con = ds.getConnection();
            PreparedStatement st = con.prepareStatement("select ? from product")
        ) { 
            String column = request.getString("column");
            st.setString(1, column);
            try (ResultSet rs = con.executeQuery()) {
                while (rs.next()) {
                    :
                }
            }
    } catch (NamingException | SQLException e) {
        // エラー中断の処理
    }
参考

JPCERT CC FIO04-J. 不要になったリソースは解放する

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

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