2022/12/20

JEP 437: Structured Concurrency

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

このエントリーは Java Advent Calendar の20日目のエントリーです。

qiita.com

 

Java 19のJEP 425: Virtual Threadsの裏で、ひっそりと登場していたのが、JEP 428 Structured Concurrency です。Virtual ThreadはPreview JEPでしたが、Structured ConcurrencyはIncubator JEPです。

そして、Java 20では、Virtual ThreadsはSecond Previewとして JEP 436 になっています。同じようにStructured ConcurrencyもSecond Incubatorとして JEP 437 になりました。

JEP 428とJEP 437の違いはほとんどないので、このままいけば次のLTSであるJava 21で正式に導入されると思います。

 

動機

Structured Concurrencyは名前にConcurrencyとついていることから分かるように、並列/並行処理に関するAPIです。構造化した並列とはどういうことなのでしょう。

それを考えるために、ちょっとしたサンプルで考えてみます。

最近は、複数の外部のサービスを組み合わせて使うシステムが多くありますね。外部にあるということは、何らかのI/Oが発生して待ちがあるということです。

たとえば、HTTPで通信するWebサービスを2つ使用し、クエリ結果を組み合わせるようなサンプルを考えてみます。

HTTP通信が発生するので、逐次的に処理をしてしまうととても時間がかかってしまいます。そこで、クエリを非同期に行います。

すごい単純ですが、こんな感じ。

    String query(final String queryText) throws InterruptedException, IOException {
        var client = HttpClient.newHttpClient();
        var request = HttpRequest.newBuilder()
                .uri(URI.create(queryText))
                .build();
        var response = client.send(request, BodyHandlers.ofString());

        var status = response.statusCode();
        if (status / 100 != 2) {
            throw new IOException("HTTP Error: " + status);
        }

        return response.body();
    }

    record Result(String result1, String result2) {}

    Response complexQuery(final String queryText1, final String queryText2)
            throws InterruptedException, ExecutionException {

        try (var pool = Executors.newFixedThreadPool(2)) {
            Future<String> future1 = pool.submit(() -> query(queryText1));
            Future<String> future2 = pool.submit(() -> query(queryText2));

            String result1 = future1.get();
            String result2 = future2.get();

            return new Result(result1, result2);
        }
    }

TwoTaskクラスではHTTPのGETでクエリーを行いますが、それぞれを非同期に行っています。クエリーのURLが引数のqueryText1とqueryText2です。

これを実行すると、特に問題がなければ普通に動くはずです。

しかし、このコードには複数の問題があります。

  1. 1番目のクエリーで例外が発生した場合、complexQueryメソッドを抜けてしまうが、2番目のクエリーは実行したままになってしまう
  2. complexQueryメソッド実行中に割り込み(Thread.interrupt()メソッドがコール)されるとメソッドを抜けるが、クエリーは実行したままになってしまう
  3. 1番目のクエリーを実行中に、2番目のクエリーで例外が発生しても、1番目のクエリーが完了しない限り(future1.get()が戻らない限り)、2番目のクエリーの例外を扱うことができない

正常に動作していればいいのですが、例外や割り込みが発生した時に、それらが正しくサブタスクに伝播されないことに問題があります。

もちろん、割り込みが発生したらサブタスクにも割り込みしたり、あるサブタスクで例外が発生したら他のサブタスクをキャンセルするという処理を書くこともできます。とはいうものの、こういうボイラープレート的な記述が増えてくるのは避けたいところです。

そこで、サブタスクを構造的に扱い、例外や割り込みなどを容易に扱えるようにしたのが、JEP 437 Structured Concurrencyなのです。

 

Structured Concurrency

Structured Concurrencyで提供されるメインのクラスはStructuredTaskScopeクラスです。

JEP 437はIncubator APIなので、今のところパッケージはjdk.incubator.concurrentですが、正式なAPIになった時にはパッケージは変更になります。たぶん、java.util.concurrentに含まれると思いますが、あくまでも予想です。

StructuredTaskScopeクラスは、名前の通りスコープです。そのスコープの中で作られたサブタスクを構造化するという使い方をします。

たとえば、先ほどのサンプルのcomplexQueryメソッドをStructuredTaskScopeクラスで書き直したものが以下になります。

    Result complexQuery(final String queryText1, final String queryText2)
            throws InterruptedException, ExecutionException {

        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            Future<String> future1 = scope.fork(() -> query(queryText1));
            Future<String> future2 = scope.fork(() -> query(queryText2));

	    scope.join();
	    scope.throwIfFailed();

            String result1 = future1.resultNow();
            String result2 = future2.resultNow();

            return new Result(result1, result2);
        }
    }

ここでは、StructuredTaskScopeクラスのサブクラスで内部クラスのShutdownOnFailureクラスを使用しています。

try-with-resources構文で記述されたブロックがStructuredTaskScopeクラスのスコープになるわけです。

このスコープの内部でサブタスクをforkし、サブタスクの完了を待つためにjoinしています。

その次のthrowIfFailedメソッドはShutdownOnFailureクラスのメソッドで、サブタスクで例外が発生した場合、他のサブクラスに伝播させタスクをキャンセルさせます。

ShutdownOnFailureというのはサブタスクで例外が発生した時に、サブタスクをキャンセルし、スコープをシャットダウンするためのクラスになります。

その後、サブタスクの結果を取得するために、getメソッドではなくresultNowメソッドを使用しています。Future.resultNowメソッドも新たに定義されたメソッドです。タスクが完了していれば結果を返し、キャンセルされた場合はキャンセルされた時点での結果を返します(結果がないこともあります)。

StructureTaskScopeクラスはVirtual Threadでの使用が前提になっており、デフォルトでVirtual Threadを使うようになっています。

なお、ここではforkしているのは2つのタスクだけですが、さらに多くのタスクをforkすることも可能です。

 

ところで、forkしてjoinするというAPIは他にもありましたね。

そう、Fork/Join Frameworkです。

前回のエントリーでも書きましたが、Fork/Join Frameworkは計算処理などI/O処理を含まないタスクがターゲットでした。一方で、Structured ConcurrencyはVirtual Threadを使うことからも分かるように、I/O処理を含むタスクにフォーカスしています。

この2つのAPIは用途に応じて使い分けるようにしましょう。

 

さて、このサンプルをコンパイルし、実行してみましょう。

Structured ConcurrencyはIncubator JEPなので、使用するにはオプションとして--enable-previewが必要です。コンパイル時に--enable-previewを使用するには--releaseも必要なので、こちらも指定します。

また、Incubatorのため、jdk.incubator.concurrentモジュールがデフォルトでは読み込まれません。そのために--add-modulesでモジュールを指定する必要があります。

このサンプルがTwoTaskクラスだったとした場合、次のようにコンパイル、実行を行います。

> javac --release 20 --enable-preview --add-modules jdk.incubator.concurrent TwoTask.java
警告:実験的なモジュールを使用しています: jdk.incubator.concurrent
警告1個

> java --enable-preview --add-modules jdk.incubator.concurrent TwoTask

せっかくなので、例外も出してみましょう。一方のクエリーだけ失敗するようなURLを使って実行してみると、単にExecutorServiceを使っていた場合、java.util.concurrent.ExecutionException例外がスローされるのに時間がかかります。

ところが、StructuredTaskScopeクラスを使った場合、すぐに例外がスローされます。これが例外が発生させた時に、他のサブタスクをキャンセルさせて、スコープをシャットダウンさせているためです。

 

ところで、StructuredTaskScopeクラスには、もう1つサブクラス兼内部クラスのStructuredTaskScope.ShutdownOnSuccessクラスがあります。

先ほどのShutdownOnFailureクラスは例外が出た場合はシャットダウンします。逆にいうと、例外が出ない場合は複数のサブタスクがすべて完了するまでjoinメソッドはブロックします。

これに対し、ShutdownOnSuccessクラスはサブタスクのうち、どれか1つが完了すれば、他のサブタスクはキャンセルして、シャットダウンします。

ShutdownOnFailureクラスはサブタスクのANDであるのに対し、ShutdownOnSuccessクラスはORになるとも言えますね。

たとえば、先ほどのcomplexQueryメソッドを早く完了したタスクの結果を返すように書きかえてみましょう。

    String complexQuery(final String queryText1, final String queryText2)
            throws InterruptedException, ExecutionException {

        try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
            Future<String> future1 = scope.fork(() -> query(queryText1));
            Future<String> future2 = scope.fork(() -> query(queryText2));

            scope.join();
            return scope.result();
        }
    }

ShutdownOnFailureクラスと異なり、ShutdownOnSuccessクラスはサブタスクの結果を扱うため、ジェネリクスでサブタスクの戻り値の型を指定します。

最も早く完了したサブタスクの処理結果を取得するのがresultメソッドです。

他はShutdownOnFailureクラスと同じです。

 

Structured Concurrencyでは他に、サブタスク間で共有できる値を扱うScopedValueクラスがあるのですが、これはまた別の機会に紹介します。

2022/12/09

Virtual Thread導入の背景 - Javaのマルチスレッドの歴史を振り返る

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

このエントリーは Java Advent Calendar の9日目のエントリーです。

qiita.com

 

Virtual ThraedはJava 19でPreview (JEP 425)、Java 20でSecond Preview (JEP 436)となり、うまくいけば次のLTSであるJava 21で導入予定です。

パフォーマンスを考える時に、一般的にはスループットと応答性の2つがあります。スループットは単位時間あたりにどのくらいリクエストをさばけるか、応答性は処理のリクエストから結果が帰るまでの時間です。Virtual Threadのこの2者のうち、スループットを向上させるために導入されます。

では、なぜ今になってVirtual Threadが導入されるのかということを、歴史を振り返りながら考えてみるのがこのエントリーです。

 

いにしえの時代 - Java 1.0からJ2SE 1.4

Javaが発表されたのが1995年。その当時の代表的なCPUといえばIntel Pentiumです。Pentiumのリリースが1993年、Pentium Proが1995年、Pentium IIが1997年です。

つまり、その当時のPCはほとんどがシングルCPU、もちろんシングルコアです。

そんな時代にあって、Javaはマルチスレッドを標準の言語機能としたことは驚きです。当然ながら、その当時はコアは1つなのでパラレル処理ではなく、コンカレント処理になります。

 

Javaのスレッドは、OSのスレッドに対応します(初期のSolarisはちょっと違いましたが、J2SE 1.2でOSのスレッドを使うようになりました)。Javaのスレッドが複数あったとしても、CPUは1つなのでOSのスレッドは1つです。したがって、Javaのスレッドをすべて実行するにはスレッド切り替えが必要です。このスレッドの切り替えのことをコンテキストスイッチと呼びます。

このコンテキストスイッチは比較的重い処理なので、頻繁に行うと性能劣化の原因になります。

とはいうものの、複数のスレッドを実行するには何らかの実行スケジューリングが必要になります。スレッドのスケジューリングにはラウンドロビンやタイムシェアリングなどがあります。

Javaのそれは単純で、どのようにスレッドが実行されるかはJVMSには明記されていません。スレッドに優先度も設定できますが、優先度が高いからといって優先的に実行されるとは限りません。

また、やっかいだったのが、タスクを記述するためのRunnableインタフェースは返り値を返せないところです。そのため、処理の結果は複数のスレッドでアクセスできるように同期化したオブジェクトを用意する必要がありました。

同期化のために使用できたのがsynchronizedブロック(もしくはsynchronizedメソッド)です。

synchronizedブロックはモニタというロックを使用して同期化を行います。synchronizedメソッドの場合、ロックするオブジェクトは指定しませんが、thisがロック対象になります。

たとえば、スレッド間のやりとりによく使用されるブロッキングキューを作成してみましょう。

ブロッキングキューはキューがいっぱいの時に要素を追加しようとすると、要素が追加できるまで処理をブロックします。同様に要素を取り出そうとした時に要素がなければ、要素が追加されるまで処理をブロックするキューです。

Javaが標準で提供しているブロッキングキューのArrayBlockingQueueクラスなどはかなり複雑なので、ここではキューのサイズが1つのシンプルなブロッキングキューを作ってみます。

キューがいっぱいの時や、空の時に待ち状態にするにはwaitメソッドを使用します。そして、Object.wait状態にあるスレッドを起こすには、Object.notifyAllメソッドを使用します。notifyメソッドもあるのですが、notifyメソッドだと1つの目的のスレッドを起こせるとは限らないので、通常はすべてのスレッドを起こすnotifyAllメソッドを使用します。

public class SingleBlockingQueue<T> {
    private T x;

    public synchronized void push(T x) throws InterruptedException {
        while (this.x != null) {
            System.out.println("Wait - push: " + x);
            wait();
            System.out.println("Wake up - push");
        }

        System.out.println("NotifyAll - push: " + x);
        this.x = x;
        notifyAll();
    }

    public synchronized T pull() throws InterruptedException {
        while (this.x == null) {
            System.out.println("Wait - pull");
            wait();
            System.out.println("Wake up - pull");
        }

        T result = x;
        x = null;
        System.out.println("NotifyAll - pull: " + result);
        notifyAll();

        return result;
    }
}

notifyAllメソッドで起こされるのはすべてのスレッドなので、本当に起こされるべきスレッドではないこともあります。そこで、while文でチェックを毎回行っているわけです。

では、このSingleBlockingQueueクラスを使ってみましょう。

public class QueueTest {
    static SingleBlockingQueue<String> queue = new SingleBlockingQueue<>();

    public static void main(String[] args) throws Exception {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    queue.push("A");
                    queue.push("B");
                    queue.push("C");
                } catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    queue.pull();
                    queue.pull();
                    queue.pull();
                } catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
            }
        }).start();
    }
}

これを実行すると次のようになります。

> java QueueTest
NotifyAll - push: A
Wait - push: B
NotifyAll - pull: A
Wait - pull
Wake up - push
NotifyAll - push: B
Wait - push: C
Wake up - pull
NotifyAll - pull: B
Wait - pull
Wake up - push
NotifyAll - push: C
Wake up - pull
NotifyAll - pull: C

Aをpushした後、Bをpushしようとしますが、すでに要素がキューにあるのでwaitします。すると、別スレッドがAをpullし、notifyAllメソッドをコールします。

キューに要素がなくなったので、waitしていたスレッドがnotifyAllで起き、Bをpushします。

といような流れで処理が進みます。

このように、synchronizedを使用するとアクセスできるスレッドを制限することができます。しかし、あるスレッドがsynchronizedブロックを実行している間、他のスレッドは処理待ち状態になってしまいます。

この当時は、想定しているスレッド数はたかがしれていました。しかし、同時に動作できるスレッド数が増えてくると、synchronizedブロックがボトルネックになってしまいます。

 

Java SE 1.0から1.4までの時代をまとめると

  • スレッドを実行するには、OSのスレッドとの対応付けが必要
  • スレッドの実行スケジューリングはシンプルなものしかなかった
  • 必要に応じて、明示的に他のスレッドに処理を移す処理を記述する
  • タスクはRunnableインタフェースで記述するため、処理の結果を直接返すことはできない
  • 複数のスレッドからアクセスするにはsynchronizedが必要

 

マルチコア黎明期 - Java 5, Java 6

Java 5がリリースされたのは2004年、Java 6は2006年。このころ、ちょうどAMDやIntelがマルチコアのCPUをリリースし始めたころです。

AMDがAthlon64 X2で先行し、Intelが1つのパッケージに2つのダイをのせたPentium Dをリリースし、その後にちゃんとしたマルチコアのCore 2をリリースしたなんてこともありました。また、Intelはハイパースレッディングで1つのコアで2つのスレッドを処理できるようになっています。

それに合わせたわけではないと思いますが、Java 5ではご存じの通りConcurrency Utilities (java.util.concurrentパッケージ)が導入され、マルチスレッドに関するAPIが大幅に拡充されました。

主なAPIをあげてみましょう

  • スレッドプール
  • 非同期処理を結果を返すCallableとFuture
  • 多様なロックのAPI
  • スレッドセーフなコレクション
  • プリミティブ型に対するアトミック処理

スレッドプールであるExecutorServiceインタフェースが導入されたことで、スレッドとタスクが切り離され、スレッドを直接生成する必要はなくなりました。

Executorsクラスが作成するスレッドプールはJava 5では4種類ありましたが、一般的には以下の2つのメソッドで作成するスレッドプールを使用します。

  • newFixedThreadPoolメソッド
  • newCachedThreadPoolメソッド

newFixedThreadPoolメソッドで作成するスレッドプールはメソッド名の通り、指定した数しかスレッドを作成しません。通常はマシンのトータルのCPUのコア数よりも少ないスレッド数を指定します。トータルのコア数を超えるスレッドを作成してしまうとコンテキストスイッチが発生してしまいます。

スレッドに割り当てられないタスクはキューで管理されます。タスクがスレッドに割り当てられれば、コンテキストスイッチが発生しないので応答性を高めることができます。

 

一方のnewChachedThreadPoolメソッドではタスクが登録された時にスレッドが余っていれば新たにスレッドを作成することはありません。しかし、余っているスレッドがない場合はスレッドを作成して、タスクに割り当てます。

このスレッドプールはWebアプリケーションなどでリクエストごとにスレッドを作成するThread per Request方式で使われることが多くあります。Thread per Requestはスループットを向上させることが可能です。しかし、応答性は低下します。また、リクエストごとにThreadオブジェクトを生成してしまうので、ヒープの消費も大きくなります。

 

ExecutorServiceインタフェースで扱えるタスクには、Runnableインタフェースに加え、Callableインタフェースが導入されました。Callableインタフェースを使うと非同期処理の結果を返せるようになります。これに伴い、非同期処理の結果を取得するためのFutureインタフェースが導入されています。

こんな感じで使います。

    ExecutorService pool = Executors.newFixedThreadPool(4);

    Future<String> future = pool.submit(new Callable<>() {
        @Override
        public String call() throws Exception {
            String result = // 何らかの処理
            return result;
        }            
    });

    String result = future.get();

    pool.shutdown();

結果が返せるということは、複数のスレッドで結果をやりとりするためにだけ使っていたsynchronizedブロック(もしくはsynchronizedメソッド)を使用しなくてもいいということです。synchronizedが減らせれば、マルチスレッドのボトルネックを減らすことができます。

ただし、Future.getメソッドは、非同期処理が完了して値が戻るまでブロックしてしまいます。この点は気を付ける必要があります。

 

さらに、セマフォなどのロックのAPIが導入されています。

特にReentrantLockクラスはsynchronizedと同様の動作をするので、synchrnozedを減らすことができます。ReentrantLockクラスは実行時に必要のないロックはロックを外すなどの最適化が行われるため、synchnorzedよりも効果的です。

 

他のスレッドセーフなコレクションやアトミック処理APIはスレッド数が少ない場合は有効に使えるのですが、多くのスレッドが同時にアクセスするような場合はボトルネックになりがちです。この当時はまだ同時に扱えるスレッドが少なかったので、よかったのですが、コア数が増えて同時に動作するスレッドが増えれば増えるほど、使い方を考えなくてはいけません。というか、多くのスレッドが同時にアクセスするような場合は、なるべく使わない方が賢明です。

 

さて、Java SE 5, 6時代をまとめると。

  • スレッドプールは応答性重視とスループット重視の2種類
  • Callable/Futureの導入で非同期処理の結果を返せる
  • 非同期処理の結果の受け渡しのためにだけ使用していたsynchronizedは減らせる
  • synchronizedではなく、ロックAPIを使用することでロックの最適化が可能

 

マルチコア実用期 - Java SE 7

Java 6がリリースされた後、ラムダ式導入でもめたり、Sun MircrosystemsがOracleに買収されたりなどあって、Java 7のリリースは大幅に遅れ、2011年にやっとリリースされました。結局、ラムダ式はJava 8に延期されたのはご存じのとおりです。

この当時、インテルは2010年に6コアのCPUをリリース、一方のAMDは2011年に8コアのCPUをリリースしています。ノートPCでもマルチコアが当たり前になってきたのもこの頃ですね。

 

さて、ラムダ式がJava 8にスリップしたということで、Java 7ではあまり大きな変化はないように思えますが、マルチスレッドに関しては大きな変化がありました。それがFork/Join Frameworkの導入です。

Fork/Join Frameworkは主に計算処理や再帰処理の効率化を行うために導入されました。たとえば、ソートや行列計算などです。

たとえば、Java SE 6まで標準APIで使われていたマージソートを考えてみましょう。

マージソートはソートを行う範囲を半分に分割していき、ソートする範囲を小さくしていきます。小さい範囲でまずソートを行い、それを組み合わせて徐々に大きい範囲のソートを行うアルゴリズムです。

このような処理の方法を分割統治法と呼びます。

現在のJavaの標準APIのソートはティムソートに変更されていますが、ティムソートも分割統治法を用いたソートアルゴリズムです。

分割統治法で分割したタスクを非同期に実行するようにすれば、マルチスレッドで処理することができます。しかし、Java 6までのスレッドプールはタスクスケジューリングが単純で、多くのタスクを非同期に実行させなければならない分割統治法では有効にスレッドを活用することができません。

そこで、Fork/Join Frameworkでは、Work Stealingを採用したタスクスケジューリングが導入されました。

それまではタスクを1つのキューで管理していましたが、Work Stealingではスレッドごとにタスクキューを持ちます。このキューはFIFOではなく、両端キューで構成されます。

スレッドが自身のタスクキューからタスクを取り出す場合は、キューの先頭から取り出します。

もし、あるスレッドのタスクキューが空になった場合、そのスレッドは他のスレッドのタスクキューからタスクを取り出す(他のスレッドから取り出すのでSteal、つまり盗むということです)ことができます。この時、タスクキューの先頭から取り出すのではなく、キューの最後から取り出します。

分割統治法では大きい粒度のタスクの処理中に分割したタスクを再びキューに登録していきます。このことは、タスクキューの先頭に近いほどタスクが大きく、最後になればなるほどタスクが小さくなることを示しています。

このため、他のスレッドが盗んでいくタスクは小さい粒度で短時間で処理ができるはずです。このようにすることで、複数のスレッドで効率よく分割したタスクを処理していくことができます。

Work Stealing方式のスレッドプールは、Java 8で導入されるパラレルストリームでも使用されます。

 

Fork/Join Frameworkは計算処理の効率化はできるものの、Webアプリケーションのような通信やDBへのアクセスがあるような用途には向いていません。しかし、Work Stealing方式のタスクスケジューリングを含むスレッドプールは、Fork/Join Framework以外の用途でも使われていくようになりました。

このスレッドプールはForkJoinPoolクラスで使用できます。デフォルトではトータルのCPUのコア数だけスレッドを生成します。このため、タスクスイッチは頻繁に行うものの、スレッドのコンテキストスイッチは抑えることができます。

また、Executors.newWorkStealingPoolメソッドでもForkJoinPoolオブジェクトを取得できます。ただし、このメソッドはJava 8以降で使用することができます。

 

マルチコア熟成期 - Java SE 8

Java SE 8は2014年にやっとリリースされました。Java 8で最も注目されたのがProject Lambda、つまりラムダ式とストリームです。マルチスレッド的にはパラレルストリームの導入もあります。

しかし、ここで取り上げるのはパラレルストリームではなく、CompletableFutureです。く

Java 7のFork/Join Frmeworkで計算処理は効率よく行うことが可能になりました。しかし、通信やDBアクセスなどのI/O処理を含むようなタスクには向いていません。

I/O処理は処理をブロックします。たとえば、Readerオブジェクトから読み込む処理はどうでしょう。

    try (BufferedReader reader = new BufferedReader(...)) {
        int c = reader.read();
    }

readメソッドは文字が読み込めるまでブロックします。Readerオブジェクトがソケットから生成されていれば、通信が届かない限り文字を読み込めません。その間、ブロックします。

I/O処理はCPUの計算時間に比べると多大な時間を使います。しかし、ブロックしてしまうと、その間CPUは遊んでしまうわけです。

そこで、I/O処理を非同期に行うことが考えられます。たとえば、こんな感じです。

    Future<String> future = pool.submit(() -> {
        // I/O読み込み処理

        return contents;
    });

    // 非同期処理の結果を受け取る
    String contents = future.get();

お分かりかと思いますが、これでは非同期処理の利点を生かすことができません。というのも、Future.getメソッドが非同期処理が完了するまでブロックしてしまうからです。

そこで、導入されたのがCompletableFutureクラスです。

非同期で行いたいような処理は1つだけということはあまりなく、複数の処理を連ねることが多いのではないでしょうか。

たとえば、Webアプリケーションであれば

  1. リクエストを受けて何らかのロジック処理を行う
  2. DBへのクエリを投げる
  3. クエリ結果からレスポンスを作成し、送信する

のような感じです。もちろん、こんな単純なことで済まないことも多いとは思いますが、こんな感じで処理が進むという例のつだと思ってください。

Thread per Requestでリクエストごとにスレッドを生成して、非同期に1, 2, 3の処理を行えばスループットは向上します。しかし、2のDB処理は完了するまでブロッキングしてしまいます。そのため、ブロッキングしている間、CPUは遊んでしまいます。

その間にも、リクエストがあればスレッドがどんどん増えるばかりで、あっという間にスループットは飽和してしまいます。そこで、DB処理も非同期にして... とやっていると先ほどのFuture.getメソッドのようにまたブロッキングを増やしてしまうかもしれません。

CompletableFutureクラスを使うと、このような一連の処理をラムダ式で記述し、メソッドチェーンで記述することができます。

たとえば、リクエストを受けるメソッドがhandleメソッドだとしてみましょう。引数の型はRequestクラスとResponseクラスだとします。

    void handle(final Request request, final Response response) {
        record Param<S>(S s, Response response) {}
        
        CompletableFuture
            .completedFuture(new Param(request, response))
            .thenApply(param -> {

                // 1. ロジック処理

                return new Param(logicResult, response);
            })
            .thenApplyAsync(param -> {

                // 2. 非同期でDBへのクエリ

                return new Param(queryResult, response);
            })
            .thenAccept(param -> {

                // 3. レスポンスの作成、送信

                param.response().response(responseContents);
            });
     }

ここではタプル的に2つのオブジェクトを扱うためにレコードクラスのParamクラスを作りましたが、これが必須というわけではないです。

そのParamオブジェクトを引数としてcompletedFutureメソッドでCompletableFuture生成しています。その後のメソッドチェーンで処理を連ねていきます。

thenが先頭につくメソッドは前段までの処理が完了した時に引数のラムダ式がコールバック的に呼ばれるメソッドです。ApplyやAcceptはラムダ式の引数や返り値によって異なります。

メソッド名の最後がAsyncのメソッドは、引数のラムダ式がさらに非同期に実行されることを示します。したがって、1.のロジック処理と2.のDBクエリが同じスレッドで実行されるわけではないことを示します。

このように、一連の処理を同期、非同期を交ぜながら記述できるのが、CompletableFutureクラスの特徴です。また、CompletableFutureクラスはデフォルトでForkJoinPoolを使用するので、スレッド数は抑えたまま、効率的に実行することができます。

 

このCompletableFutureクラスを使えば、Thread per Requestに比べるとスループットの飽和も抑えることができます。

しかし、残念ながら、いまだにCompletableFutureクラスはそれほど使われていません。

単にシーケンシャルに処理を記述していくのに比べると、CompletableFutureクラスで処理を記述するには考え方を変えなければいけません。処理のキャンセルや、例外の扱いなどが煩雑になるという点もあります。

さらに、CompletableFutureクラスのデバッグは難しいのです。

ストリームのデバッグをしたことがある方は分かると思うのですが、ラムダ式が含まれるとスタックトレースが読みにくくなります。

しかも、CompletableFutureクラスは処理を記述したラムダ式が同じスレッドで動作するわけでありません。スタックトレースのスタックは、スレッドが持つスタックのことです。スレッドが異なればスタックトレースは別ものになります。このため、CompletableFutureクラスにおいて、ラムダ式で記述した処理で例外が発生したとしてもスレッドが異なるため、スタックトレースも別々になり、一連の処理の流れはスタックトレースからは追うことができません。

このように、CompletableFutureクラスは使いこなすうえで難しい点があります。しかし、CompletableFutureクラスを使いこなせるのであれば、スループットの高いシステムを実現することができます。

これはSpring WebFluxのようなリアクティブ系のフレームワークでも同じです。使いこなせるのであれば、高いスループット性能が可能です。

そして、CompletableFutureクラスでも、リアクティブなフレームワークでも使いこなせるのであれば、Virtual Threadは使わなくても大丈夫なはずです。

 

ここまでのJavaでのマルチスレッドの流れをまとめてみると

  • Thread per Requestを使えば、スループットを上げることは可能だが、飽和するのも早い
  • 計算処理など処理をブロックする要因がない処理であればFork/Join Frameworkもしくはパラレルストリームで応答性を向上することができる
  • I/Oなど処理をブロックする要因がある場合はブロックする処理を非同期に処理する。そのためにCompletableFutureクラスやリアクティブ系のフレームワークが使用可能
  • しかし、CompletableFutureクラスやリアクティブ系のフレームワークは使いこなすのが難しく、デバッグもやりにくい

ここらへんを何とかしてくれるのがVirtual Threadです。

Concurrency Utilities以降のJavaでは、自分でスレッドを生成することはなくなっているはずです。Virtual Threadも自分で生成させることはないはずです。

フレームワークなどがVirtual Threadに対応すれば、コードを変更しなくても自動的にVirtual Threadを使えるようになっているはず。

Springが早々にVirtual Threadに対応することを発表しているように、意外にVirtual Threadを使えるようになる日も近いかもしれません。

 

おまけ

わざわざ、Java 1.0のころの古いスレッドん使い方を説明したのは、ここで説明したことはほとんどがVirtual Threadではバッドマナーになるからです。

Virtual Threadは、Virtualではないスレッドの上で動く仮想スレッドです。しかし、Java 1.0のころのsynchronizedやwait/notifyなどの操作は仮想スレッドではなく、仮想スレッドが動作しているスレッドに対して行われます。

スレッドがブロックしてしまったりすると、スレッドの上で動作する複数のVirtual Threadすべてが影響を受けてしまいます。

ThreadクラスのいくつかのAPIは@deprecatedになり、使うことが非推奨になっています。

 

Threadクラスの非推奨(@deprecated)のAPI

  • checkAccess
  • countStackFrames
  • getId
  • resume
  • stop
  • suspend

これらのメソッドはいつ削除されても不思議はありません。

ちなみに、Java 8では使えていたdestroyメソッドとstop(Throwable)メソッドはすでに削除されています。

また、synchronizedブロック(メソッド)はロックAPIで書きかえられます。wait/notifyを使う場面はほとんどないとは思いますが、なるべくタスクは独立に処理できるようにし、これらを使わないような設計を考えていく必要があります。

2022/11/15

JavaOne 2022に行ってきました

このエントリーをはてなブックマークに追加
_DSC7423
10月18日から3日間にわたって開催されたJavaOne 2022に参加してきました。
今年は久しぶりにJavaOneという名前が復活し、ラスベガスでの初めての開催ということになりました。

技術的なことはさておき、初のラスベガス開催ということで、来年参加する人たちの参考になればということで備忘録を書いておきます。

現地まで

残念ながら日本からラスベガスまでの直行便はありません。日本からだと以下の3つが主な乗り継ぎ空港ですね。
  • ロスアンゼルス
  • サンフランシスコ
  • シアトル
今回は知り合いの日本人参加者ではロス乗り継ぎが多かったです。私はサンフランシスコで乗り継ぎました。

飛行機代を抑えたいのであれば、LCCのZipairがロスアンゼルスやサンノゼまで飛んでいるので、それを利用するというのもありかもしれません。

アメリカ入国

どの空港で乗り継いだとしても入国審査は乗り継ぎの空港、私の場合はサンフランシスコ空港で行います。

アメリカに入国するにはESTAが必要です。ESTAは2年間有効で、ESTAを申請したはじめての時は有人の入国審査になりますが、その後は自動入国審査端末での審査になります。

今年はコロナになってから久々のアメリカだったので、ESTA申請し、審査官による入国審査でした。アメリカの入国審査官ってなんであんなにいろいろ聞いてくるんでしょうね😰

そして、来年はどうなるか分からないですが、今年はコロナに関する宣誓書を書いておく必要がありました。また、コロナワクチンの接種証明はいつでも見せられるようにしておきました。実際には見せる機会はありませんでしたが、念のため。

接種証明はマイナンバーカードを持っている人であれば、スマホの接種証明書アプリが便利です。マイナンバーカードを持っていない場合は、役所で英語表記の接種証明書を入手します。

ラスベガスまで

入国した空港から、ラスベガスのハリーリード国際空港(以前はマッカラン国際空港でしたが去年名称が変わりました)までは国内線で乗り継ぎます。

ハリーリード空港はターミナル1と3があります。私はUnited Airlinesだったのでターミナル3でした。

ハリーリード空港はいたるところにスロットマシンが置いてあって、さすがカジノの街という感じです。

ターミナル3はゲートがある建物と荷物カウンター(Baggage Claim)がある建物が別々になっていて、トラムで結ばれています。ただ、トラムが荷物カウンターに向かうものと、ターミナル1に向かうものの2種類あるので間違わないようにしないといけません。

大きい階段があって、Welcome to Las Vegasと書いてある看板?を下っていった先にあるのが、荷物カウンター行きのトラムです。

荷物を受け取ったら街に向かいましょう。
_DSC0834

ホテルまで

ハリーリード空港は市街地まではUberやLyftなどのライドシェア、タクシー、バスなどの選択肢があります。

タクシーの場合、空港から市街地は定額で$27だそうです。これに空港使用料とチップが加わります。

ライドシェアは混雑する時間帯はタクシーとあまり変わらない値段ですが、混雑していない場合はチップも含めても$20ぐらいです。

ライドシェアはどこでも呼べるわけではなく、空港内の決められた場所でしか乗車できないようになっています。

ターミナル3の場合、乗車できるのはパーキングの建物の中にあります。

荷物カウンターの階(Level 0)から1つあがって、49番の出口を出るとパーキングに向かう橋があります。パーキングについたら、1つ下の階(Level V)にライドシェアの乗車場があります。

乗車場についたらUberやLyftのアプリを使って、行き先と車の種類を指定して、配車できます。

ホテル

ラスベガスは南北に走るストリップを中心に街が構成されています。ストリップでも中心に なるのがフォーコーナー (Four Corners)と呼ばれる交差点です。

おおざっぱにいうとフォーコーナーに近い方がホテルの宿泊費が高く、離れると安くなる傾向があります。特に東西方向はストリップからちょっと離れるだけで急に安くなるようです。

JavaOneの会場はフォーコーナーにほど近いシーザースフォーラムというところです。そこのため、ホテルを選ぶには2つの選択肢があると思います。
  • 会場に近くて便利だが、宿泊費は高いホテル
  • 会場から遠いが、宿泊費は安いホテル
宿泊費は高いといっても、今までのサンフランシスコに比べればかなり安いです。

私は今回、会場に近いフラミンゴ(Flamingo)に宿泊しました。フラミンゴは古いホテルなので設備とかはかなりイマイチな感じ。古いホテルのためか、フォーコーナー近くのホテルの中では比較的安いです。

会場に近いので、ノベルティもらって荷物が多くなった時など、部屋に戻って荷物を置いてくるなんてことができるのは便利です。

フラミンゴやベネチアン(The Venetian)などは、JavaOne登録と一緒にホテルの予約もできるので、それを利用するというのも1つの手です。カンファレンスレートで、通常よりは少しだけ安くなっているはずです。

気候

ラスベガスは砂漠の中にあります。10月でしたが、最高気温は30度ぐらい。最低気温が15度ぐらいで、朝晩は過ごしやすいです。乾燥しているので、昼でも直射日光に当たらなければ、意外に過ごしやすかったです。

気になるのが会場の冷房だったのですが、意外にもそんなに冷えてはいませんでした。それでも、1枚羽織るものがあるといいと思います。

治安

最近、アメリカ各地で治安が悪くなったと聞きますが、ラスベガスのストリップ近辺は特に治安が悪い感じは受けませんでした。

ストリップからちょっと離れたところには治安の悪いところもあるらしいですが、そこまで行くことはないでしょうから、治安について心配することはないようです。

もちろん、ホームレスはいますが、お金をせびってくるようなホームレスには遭遇しませんでした。とはいっても、日本の感覚でいるのは危険なので、注意は必要です。

SIM

今まで海外旅行に行くときには、プリペイドSIMを普段使っているSIMと入れ替えて使っていました。

今回はじめてeSIMを使用しました。日本でプリペイドのeSIMを購入して、事前に設定しておきました。後はアメリカについたら、eSIMを有効化すればいいだけなので、とても楽。

プリペイドeSIMもいくつか選択肢がありますが、今回はソラコムのグローバルeSIMを使用しました。ソラコムのeSIMはデータ通信だけなのですが、通話はしないので全然OKです。

プリペイドeSIMを提供している企業によっては通話もできるところがあるようなので、必要であれば探してみるのもいいかもしれません。

なお、会場やホテルではWi-Fiが使えます。今回、会場のWi-Fiが瞬断することがありましたが、使えない状態が続くとか、遅くて使えないということはありませんでした。

まぁ、今回は参加人数が少なかったからということもあるかもしれません。

会場

今回のJavaOneの会場はCaesars Forumで、CloudWorldはThe Venetian Expoで行われました。両会場は橋でつながっていて行き来できるようになってます。

本来なら、3年前にCaesars Forumのこけら落としがOpenWorld/Code Oneだったんですけど、コロナで延期。まぁ、しかたありません。


Caesarsと名前がついていますが、Caesars Palaceからは離れていて、Harrahsの東側にあります。HarrahsやLINQからは直接Caesars Forumに入ることができ、またFlamingoの北側の道から観覧車のHigh Rollerの真下にCaesars Forumへ入る通路があります。

The Venetian ExpoはCaesars Forumの北側で、The Venetianから直接入ることができるのですが、カジノを通り抜けなくてはいけなくて、これがまた迷路のようになっているので、迷わずにいくのはなかなか大変です。

先述したように、会場はWi-Fiが使えます。また、今回は朝食とランチが提供されて、Caesars Forumの南側のテラス席でも食べることができました。暑くなければ、いい感じなんですけどね。

食事

ラスベガスは観光客向けの飲食店はいたるところにあります。でも、なんかみんな観光客向けの大箱、かつバックに資本がついているところばかりなような気がするんですよね。もちろん、そういう需要があることは分かるのですが、私の嗜好とは合わないのです。

ファストフード

1人でさくっと行きやすいといえば、ファストフードですね。McDonald'sやBurger Kingなどのように日本でも展開しているチェーン店もありますが、せっかくならば日本にない、もしくは日本ではまだそれほど店舗が多くないところを選んでみるのはどうでしょう。

  • In-N-Out Burger
  • Chick-fil-A
  • Chipotle Mexican Grill
  • Panda Express
In-N-Out (イン アンド アウト)はハンバーガーチェーンですが、裏メニューのカスタムがいっぱいあることや、冷凍のポテトを使わないなどで有名なところです。

Caesars Forumからも近いのですが、人気なのでいつも行列してました。とはいうものの、回転は早いので、そんなに待つこともないと思います。

Chick-fil-A (チックフィレイ)はチキンバーガーのチェーン店。KFCのチキンフィレバーガーのような感じです。Chik-fil-Aも人気ありますね。

Chipotoleはブリトーやタコスなどのメキシカンのチェーン店。ファストフードとしては珍しい地産地消をうたっています。日本からのJavaOne参加者にもここのファンという人が多いです。

最後のPanda Expressはアメリカンな中華のチェーンです。名物はオレンジチキン。日本では1度撤退していますが、2016年に再進出してます。でも、まだ店舗はそんなにないので、アメリカで行ってみるのはありだと思います。

フードコート

フードコートも1人で行くにはいいですね。だいたいのカジノにはフードコートがあるようです。

たとえば、Cloud World会場のベネチアンには2つのフードコートがあり、ハンバーガーのJohnny RocketsやFat Burger、上にも書いたPanda Expressなどがあります。

店を探す

やっぱりファストフードはやだという場合には店を探すわけですが、そんな時に役に立つのがアメリカ版食べログとでもいえるYelpです。
だいたいの店が予約可能なので、できることなら予約してから行った方がいいと思います。そんな時に使えるのが、OpenTableです。
日本のトレタやTableCheckのようなものですが、OpenTableの方が古くからあり、店の検索などもずっとやりやすくなっています。

今回もOpenTableで予約をとっていったところが多かったです。ただし、ホテル内にあるレストランはホテルの予約システムを使っていることがあるので、そちらもチェックした方がいいと思います。

とはいうものの、店が多いので、目当ての店があるとかでもないと、なかなか難しいですね。

参考までに今回ラスベガスで行ったレストランを列挙しておきます。

レストラン
  • Bouchon Bistro
    ナパにあるミシュラン三つ星のThe French Laundryの姉妹店のビストロ。本店はナパにあります。ナパのBouchonにも行ったことがありますが、ラスベガスの方がフォーマルな感じですね。とはいっても、ドレスコートがあるわけではなさそうですが。
    ベネチアンの10階にありますが、テラス席まであります。ぜんぜん10階のような感じがしないですが、とても静かでいいですね。
    朝ごはんに行ったのですが、ナパのBouchonはパンがおいしいので、クロワッサン。それとメインのサーモンのグリル。デザートも朝から頼めるというので、プロフィットロール。朝からいい気になって頼んだら、1万円超えてしまいました😰 でも、おいしかったので、OK。
    _DSC5763 _DSC5810 _DSC5875
  • Bardo Brasserie
    ARIAリゾートの中にあるブラッセリー。内装がウッディーで重厚な感じ。
    ここも朝ごはんで開店と同時に入店したのですが、開店待ちの人がいっぱい。このブログを書くために調べてみたら、Michael Mina系列のブラッセリーでした。Michael Minaもスターシェフなので、この人気もわかりますね。
    ここでは、タルタルとフレンチトースト。フレンチトーストはブリオッシュなので、そんなに大きくないだろうと高をくくっていたら、超巨大でした。
    _DSC0704 _DSC0751 _DSC0822
  • Sugarcane Raw Bar Grill
    _DSC6119 _DSC6127 _DSC6160
  • Off the Strip
    _DSC6300
ハンバーガー

WahlburgersもBurger Barもチェーン店ですが、Wahlburgersの方がファストフードに近い感じで、私にはBurger Barの方がよかったです。アメリカでハンバーガー食べると、日本のグルメバーガーがいかにおいしいか分かりますね😂

Wahlburgersでハンバーガーとサラダを食べていたら、隣に座っていたおじいさんにBig Dinnerだねと言われてしまいました🤣 
  • Wahlburgers
    _DSC5704 _DSC5723
  • Burger Bar
    _DSC0528
ステーキ
ステーキは、フレンチやイタリアンなどに比べると、シェフの技量よりも材料に起因する部分が大きいような気がします。つまり、いい肉を使っているかどうか。値段が高いところはいい肉を使っているので、おいしいわけです。もちろん、例外はありますけど。

とはいうものの、2倍の値段だったら、2倍おいしいということはないので、そこらはバランスですね。

2店行きましたが、Gordon Ramsayの方が値段が高いだけあっておいしかったです。Smith & Wollenskyもおいしかったんですけどね。

ちなみに、Gordon Ramsayもスターシェフで、ラスベガスにいろいろなジャンルの店があります。最近、Fish & Chipsの店をオープンしたらしいのですが、いつも行列していたので行くのをあきらめました。来年、行こうっと!
  • Smith & Wollensky
    _DSC6413 _DSC6459
  • Gordon Ramsay Steak
    _DSC7214 _DSC7219 _DSC7260 _DSC7294
スイーツ

アメリカは、おいしいスイーツがなかなかないんですよね。ラスベガスでもそういう感じでした。

たまたま帰国する日が、シザースパレスにドミニクアンセル (Dminique Ansel Bakery)がオープンするということだったので行ってみたのですが、すごい行列。

ドミニクアンセルは以前、表参道にもあったのですが撤退してしまったので、久しぶりに食べられると思ったのですが、さすがに行列に並ぶと飛行機に遅れてしまうので断念。

来年リベンジします。
  • Sweets Raku
    アメリカでアシェットデセールの店があるとは思いませんでした。とはいっても、日本人がオーナーで、パティシエも日本人。どおりで!
    日本人が作っているということで、日本の繊細なデザートが食べられます。ラスベガスでパフェ食べらるのはたぶんここだけ。というか、日本のパフェは日本独自のデザートなので、外国ではなかなか食べられないのです。
    _DSC6075 _DSC5981
  • Dandelion Chocolate
    サンフランシスコに本店があるビーントゥバーチョコレートのDandelion Chocolate。日本にも蔵前に店があります。
    ここはチョコはもちろん、チョコを使ったスイーツもおいしいです。
    ついでですが、ここが使っているコーヒー豆がサードウェーブ系で有名なRitual。ラスベガスでおいしいコーヒーをなかなか見つけられなかったので、チョコを食べなくてもコーヒーを飲みに来るのはありですね。
    _DSC7493 _DSC7508 _DSC7520
  • Bouchon Bakery
    上に書いたBouchon Bistroのベーカリー部門です。パンもありますが、スイーツもあります。もうちょっと種類が多いといいんだけどなぁ...
    _DSC9679 _DSC9707 _DSC9766
  • Bellagio Patisserie
    _DSC6336 _DSC6353
  • Donut Bar
    _DSC5896 _DSC5917

アメリカのレストランでクレジットカードで支払う時、一度カードを持って行ってレシートを持ってきてくれます。そこにチップと合計額を書いて、サインをして完了なんですけど、それがめんどうくさい。

チップのないフランスやシンガポールだと、店員さがハンディターミナルを持ってきて、タッチ機能がついていればピッとタッチすればそれでおしまい。アメリカはチップあるし、こうはできないよなぁと思っていたのですが、Bardo Brasserieではじめてハンディターミナルで決済しました。

ハンディターミナルを店員さんが置いていって、ディスプレイにチップのパーセンテージが表示されるので、その中から選択して、後はタッチすればOK。タッチ機能がなければ、暗証番号を入れるのだと思います。

今後、こういう店が増えていくのでしょう。それにしても、チップのパーセンテージの選択肢が決め打ちというのもなんかへんな感じですね。
_DSC0826

買いもの

ラスベガスはショッピングモールやアウトレットもあるので、時間があるのであればそういうところにいけばいいと思います。

アウトレットはちょっと離れているので、バスかタクシー/ライドシェアでしょうね。

さて、問題は日用品です。水とかね。

スーパーとかコンビニ的に使えるのは、以下の2つのショップ。

  • Walgreens
  • CVS
どちらも複数の店舗がJavaOne会場近辺にあります。どちらの店舗ももともとはドラッグストアなのですが、Walgreensの方が食べものなどは多い感じ。バラマキ用のおみやげのお菓子なんかはWalgreensの方がいいかもしれません。


その他

ドルは持って行った方がいい?

ほとんどクレジットカードで支払うので、現金はほとんど使いませんでした。

レストランに複数人で行った時も、クレジットカードを4枚ぐらいまでなら受け付けてくれます。

今回、現金を使ったのは、6人で食事に行った時の支払いと、チェックアウトの日に荷物を預かってもらったので、ポーターに対してのチップぐらい。後は全部クレジットカードでした。

Google Mapsのオフラインマップ

Google Mapsは便利なので、旅行には必需品だと思うのですが、意外に知られてないのがオフラインマップ。

Wi-Fiのあるところで、事前にマップをダウンロードしておけば、ネットが使えない場所でもGoogle Mapsを使うことができますよ。

帰国時

10月の時点では帰国時にMySOSに登録しておく必要があったんですが、11月からはVisit Japan Webに統一されたようです。来年はどうなるのか分からないですね。

ちなみに、Visit Japan Webを使うと、入国審査や税関申告も事前に登録しておくことができます。税関申告の紙を書かなくてもいいというのはいいのですが、使い方がイマイチなんですよね。

申告を事前に行っておくと、2次元バーコードが表示されるので、空港の税関のところでリーダーで読ませて、そのまま出ていけるのですが、導線がイマイチ。出口のところにリーダーを置いておけばいいのにと思いました。

まぁ、でも徐々に改善されるでしょう。


さて、来年のJavaOneは同じくラスベガスのCaesars Forumで9月18日から21日。今年は3日間でしたが、1日増えて4日間の会期に戻ります。

今年はいろいろと準備ができていない様子がありありだったのでしたし、はじめての会場だったこともあり、もうちょっとなんとかならないかなぁというところも多くありました。

来年はラスベガスでも2回目ですし、会期も公開されていますから、今年よりはよくなっているでしょう。

今のところ、私は参加する予定なので、ぜひご一緒に!

2022/09/20

JEPでは語れないJava 19

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

半年ぶりのJavaのアップデートで、Java 19がリリースされます。

Java 19はJava 18よりJEPは少なく、しかもPreviewやIncubatorでないJEPはJEP 422のLinux/RISC-V Portしかありません。こんなアップデートは初めてかも。

  • 405: Record Patterns (Preview)
  • 422: Linux/RISC-V Port
  • 424: Foreign Function & Memory API (Preview)
  • 425: Virtual Threads (Preview)
  • 426: Vector API (Fouth Incubator)
  • 427: Pattern Matchning for switch (Third Preview)
  • 428: Structured Concurrency (Incubator)

JEP 424のForeign Function & Memory APIは、Java 18まではIncubatorだったのですが、なぜかPreviewに変更されました。今までのFFM APIはjdk.incubator.foreignモジュールから、java.baseモジュールに変更されて、パッケージがjava.lang.foreignになったからなのかもしれません。

なお、Java 19からはFFMに関連して、Restricted MethodsがSpecificationに記述されるようになりました。Javaが管理していないメモリへのアクセスするAPIなどがRestricted Methodsに指定されています。

JEP 426 Vector APIはなかなかIncubatorが外れずにFouthまで行ってしまいました。なかなか難しいですね。

JEP 427 Pattern Matching for switchもThird Previewです。JEP 405 Record Patternsはinstance ofでRecordのプロパティを直接扱えるようになる機能です。これもinstance ofの後はswitchでも使えるようになるはずなので、Pattern Matching for switchが正式になるのはいつになるのか...

JEP 425 Virtual ThreadsはProject Loomで策定されているAPIで、当初はFiberと呼ばれていたものです。Virtual Threadsが銀の弾丸のように書いてある記事もありますけど、実際はそんなことは全然ないんですけどね。ちゃんと設計されたマルチスレッドのシステムであれば、Virtual Threadsを使わなければならない場面は少ないはずです。

JEP 428 Structed Concurrencyは複数のタスクを例外も含めて扱いやすくするためにStructuredTaskScopeクラスを導入するJEPです。

 

ということで、PreviewやIncubatorがついていないStandard JEPの新機能というのは、Java 19ではないことになってます。しかし、JEPに記述されていないAPIの追加は意外に多いんですよね。

 

今回はjava.baseモジュールなどにPreview JEPのAPIが多く導入されていますが、JEPでの新機能は他にゆずって、ここではPreviewやIncubatorのAPIの説明は省略します。また、java.baseモジュール以外のモジュールの変更は少ないし、普通の開発ではほぼ使われないAPIなので、今回はjava.baseモジュールだけ説明します。例によってセキュリティ系のAPIの変更は櫻庭がよく理解していないので省略します。

 

廃止になったAPI

Java 19では、なんと廃止になったAPIはありません。これも珍しいですね。

 

廃止予定のAPI

Java 19で追加された廃止予定のAPIは、SwingのPluggable Look-and-Feel関連のクラスとメソッドです。

 

クラス

前述したようにSwingのPluggable Look-and-Feel関連のクラスがforRemoval=trueになりました。

  • javax.swing.plaf.basic.BasicMenuItemUI.MouseInputHandler
  • javax.swing.plaf.basic.BasicScrollPaneUI.HSBChangeListener
  • javax.swing.plaf.basic.BasicScrollPaneUI.PropertyChangeHandler
  • javax.swing.plaf.basic.BasicScrollPaneUI.ViewportChangeHandler
  • javax.swing.plaf.basic.BasicScrollPaneUI.VSBChangeListener

 

メソッド

メソッドもSwingのPluggable Look-and-Feel関連です。

  • javax.swing.plaf.basic.BasicDirectoryModel.intervalAdded
  • javax.swing.plaf.basic.BasicDirectoryModel.intervalRemoved
  • javax.swing.plaf.basic.BasicDirectoryModel.lt
  • javax.swing.plaf.basic.BasicToolBarUI.createFloatingFrame

 

追加されたAPI

Java 19で追加されたAPIはかなり多いのですが、半分以上はFFM APIです。とはいうものの、FFM以外でもかなり多くのAPIが追加されています。

 

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

java.ioパッケージではシリアライゼーション関連の例外のコンストラクタが追加されました。なぜ、今、これらのコンストラクタが追加されたのかはよく分かりません。

 

InvalidClassException例外

例外の原因を示すcauseがコンストラクタ引数で指定できるようになりました。

  • InvalidClassException(String reason, Throwable cause)
  • InvalidClassException(String cname, String reason, Throwable cause)

 

InvalidObjectException例外

こちらも同じく、例外の原因を示すcauseがコンストラクタ引数で指定できるようになりました。

  • InvalidObjectException(String reason, Throwable cause)

 

ObjectStreamException例外

こちらも同じです。

  • ObjectStreamException(String message, Throwable cause)
  • ObjectStreamException(Throwable cause)

 

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

java.langパッケージも多くのAPIが追加されていますが、Virtual Thread関連のものが多いため、それ以外のAPI変更を説明します。

 

Character.UnicodeBlockクラス

Java 19ではUnicode 14.0をサポートするようになりました。

Unicode 14.0というと敬礼の絵文字などが使えるようになっています。しかし、それ以外にも古代アルバニア文字やインドなどで使われているらしいTangsa語の文字などが追加されています。そのためにBlockとScriptも追加されています。

  • ARABIC_EXTENDED_B
  • CYPRO_MINOAN
  • ETHIOPIC_EXTENDED_B
  • KANA_EXTENDED_B
  • LATIN_EXTENDED_F
  • LATIN_EXTENDED_G
  • OLD_UYGHUR
  • TANGSA
  • TOTO
  • UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED_A
  • VITHKUQI
  • ZNAMENNY_MUSICAL_NOTATION

 

Character.UnicodeScriptクラス

こちらも同じく、Unicode 14.0で追加されたScriptが追加されています。

  • CYPRO_MINOAN
  • OLD_UYGHUR
  • TANGSA
  • TOTO
  • VITHKUQI

 

note: 以下の説明はJava 19ではなくて、Java 20の機能でした。Satoさん、ご指摘ありがとうございます。

APIの変更ではないのですが、java.text.BreakIteratorクラスの実装が拡張されて結合文字の絵文字に対応できるようになっています。

今までは正規表現の\b{g}で分解するしかなかったのですが、BreakIteratorクラスのgetChracterInstanceメソッドでできるようになったようです。詳しくはJBSのJDK-8291660をご覧ください。

   - ここまで -

 

Doubleクラス

定数が1つ追加されていますが、これはJLS 4.2.3で定義されている浮動小数点のNを表すものです。

  • PRECISION

PRECISIONはintで定義されていて、doubleでは53になっています。

 

Floatクラス

FloatクラスもDoubleクラスと同じく、JLS 4.2.3で定義されている浮動小数点のNを表す定数が追加されました。

  • PRECISION

floatでは24に定義されています。

 

Integerクラス/Longクラス

まさかIntegerクラスにメソッドが追加されるとは思いもよりませんでした。

  • static int compress(int i, int mask)
  • static int expand(int i, int mask)

Longクラスの場合は引数と戻り値の型がlongになります。

compressはmaskのビットが立った桁のみを残して、取り除いた桁は詰めて表現した数にします。Javadocにある例は

jshell> int compress = Integer.compress(0xCAFEBABE, 0xFF00FFF0)
compress ==> 830379

jshell> System.out.printf("%X%n", compress)
CABAB

maskがFF00FFF0 (1111 1111 0000 0000 1111 1111 1111 0000)なので、16進のCAFEBABEの1になっているビット桁だけにすると CA は残って FE は削除、BAB が残って E が削除。残った部分をくっつけると CABAB となるわけです。

expandメソッドは逆にマスクのビットが立っている桁に、ビットを置きなおす感じです。

jshell> int ex = Integer.express(0xCABAB, 0xFF00FFF0)
ex ==> 211860144

jshell> System.out.printf("%X%n", ex)
CA00BAB0

この演算は下位ビットから当てはめていくので、ビットの立っている箇所だけではiが表されない場合、上位ビットが省略されてしまうようです。

jshell> int ex = Integer.express(0xCABAB, 0x0000FFF0)
ex ==> 47792

jshell> System.out.printf("%X%n", ex)
BAB0

 

Mathクラス/StrictMathクラス

定数にτが追加されました。

  • static final double TAU

τは円の半径に対する円周長の比になり、値としては2πになります。

jshell> Math.TAU
$1 ==> 6.2831853071795

 

Threadクラス

ThreadクラスにはVirtual Thread関連のAPIが増えていますが、それ以外にもいくつかメソッドが追加されました。

  • boolean join(Duration duration)
  • void sleep(Duration duration)
  • long threadId()

joinメソッドとsleepメソッドはタイムアウトをミリ秒で設定できましたが、Java 19で任意の単位の時間で指定できるようなオーバーロードが追加されました。

threadIdメソッドはスレッドのIDを取得するためのメソッドです。

 

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

BigDecimalクラスに定数が追加されました。

 

BigDecimalクラス

2を表す定数が追加されています。

  • BigDecimal TWO

 

BigIntegerクラス

パラレルに乗算を行うメソッドが追加されています。

  • BigInteger parallelMultiply(BigInteger val)

this×valを行う演算ですが、数値が千を超えるようなビット数の場合、処理をパラレルで行います。

 

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

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

 

DecimalFormatSymbolsクラス

ロケールを返すメソッドが追加されています。

  • Locale getLocale()
jshell> var symbols = DecimalFormatSymbols.getInstance()
symbols ==> java.text.DecimalFormatSymbols@f6cadb1e

jshell> symbols.getLocale()
$1 ==> ja_JP

 

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

ISO 8601準拠かどうかを返すメソッドが追加されました。

 

Chronologyインタフェース

暦がISO 8601に準拠しているかどうかを返すメソッドが追加されました。

  • defualt boolean isIsoBased()

デフォルトではfalseを返しますが、ISOChronologyクラスなどはtrueを返します。JapaneseChronologyクラスなどもtrueを返します。ただし、太陰暦であるヒジュラ暦を表すHijrahChronologyクラスはfalseを返します。

 

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

ロケール指定に関してのメソッドが追加されています。

 

DateTimeFormatterクラス

ローカライズしたパターンの生成を行うメソッドが追加されました。

  • DateTimeFormatter ofLocalizedPattern(String requestedTemplate)

今までofPatternメソッドでロケールを渡すことで同じようなことができましたが、このメソッドはISO 8601準拠の暦をしようすることが前提となっているようです。引数のrequestedTemplateには通常のDateTimeFormatterのフォーマットとは異なり、正規表現で記述するようになっています。使用できる正規表現はJavadocに記載されています。

また、ResolverStyleがSMARTになります。

 

DateTimeFormatterBuilderクラス

こちらも、ロケールに応じたビルダー生成のメソッドが追加されました。

  • DateTimeFormatterBuilder appendLocalized(String requestedTemplate)
  • String getLocalizedDateTimePattern(String requestedTemplate, Chronology chrono, Locale locale)

appendLocalizedメソッドは、DateTimeFormatter.ofLocalizedPatterメソッドのために作られたメソッドのようです。ofLocalizedPatterメソッドの実装で使われていました。

getLocalizedDateTimePatternメソッドはofLocalizedPatternメソッドで指定した正規表現のrequestedTemplateから通常のDateTimeFormatterクラスで使われるパターンを取得するためのメソッドのようです。

 

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

 

HashMapクラス, LinkedHashMapクラス, WeakHashMapクラス, HashSetクラス, LinkedHashSetクラス

マップオブジェクトを生成するためのファクトリメソッドが追加されています。

  • <K, V> HashMap<K, V> newHashMap(int numMappings)

上のnewHashMapメソッドはHashMapクラスで定義されたメソッドです。LinkedHashMapクラスはnewLinkedHashMapメソッド、WeakHashMapクラスはnewWeakHashMapメソッドになります。

同様に、HashSetクラスはnewHashSetメソッド、LinkedHashSetクラスはnewLinkedHashSetメソッドになります。

これらのメソッドはいずれも空のミュータブルなマップオブジェクトを生成します。マップオブジェクトの初期容量は、引数のnumMappingsが負荷係数0.75になるように設定されます。

 

Localeクラス

3種類のファクトリメソッドが追加されています。

  • static Locale of(String language)
  • static Locale of(String language, String country)
  • static Locale of(String language, String country, String variant)

最近のJavaではファクトリメソッドにofが使われるようになっているので、Localeクラスもそれにならったようです。ofメソッドを使う方がキャッシュを使用するので、不要なオブジェクト生成を抑制できます。今後はnewでオブジェクト生成をするのではなく、ofメソッドを使うべきです。

 

Objectsクラス

どこかで見たことのあるはずな表記を作るためのメソッドが追加されました。

  • static String toIdentityString(Object o)

このメソッドを使うと、 型名@ハッシュコード 形式の文字列を作ってくれます。

jshell> var text = "ABC"
text ==> "ABC"

jshell> Objects.toIdentityString(text)
$2 ==> "java.lang.String@b1a58a3"

この形式、Java使っていたら見たことあるはずですよね。toStringメソッドのデフォルトがこの形式の文字列を返します。まぁ、だいたいのクラスはtoStringメソッドをオーバーライドしてしまいますけど。

 

Randomクラス

ファクトリメソッドが1つ追加されました。

  • static Random from(RandomGenerator generator)

RandomGeneratorインタフェースはJava 17で追加されたインターフェースです。RandomクラスはRandomGeneratorインタフェースを実装していますが、乱数生成器を指定することはできませんでした。fromメソッドを使用すると任意の乱数生成器を指定して、Randomオブジェクトを生成することができます。

 

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

java.util.concurrentパッケージもいろいろと追加されていますが、JEP 425関連のメソッドも多いです。

 

ExecutorServiceインタフェース

やっと、ExecutorServiceインタフェースがAutoClosableになりました!!

  • default void close()

AutoClosableインタフェースを実装しているということは、try-with-resources構文で記述できるようになるということです。今まで使い終わった後にshutdownメソッドをコールしなくてはいけなかったのが、解放されました。

デフォルト実装ではshutdownメソッドをコールして、awaitTerminationメソッドで終わるまで待つ実装になっています。

 

ForkJoinPoolクラス

ForkJoinPoolクラスはExecutorServiceインタフェースを実装しているので、こちらもAutoClosableになっています。それ以外に2つのメソッドが追加されました。

  • default void close()
  • <T> ForkJoinTask<T> lazySubmit(ForkJoinTask<T> task)
  • int setParallelism(int size)

lazySubmitメソッドは、名前の通りsubmitメソッドのlazy版です。最終的には実行されますが、いつ実行されるかはスレッドの空き状況によります。

ForkJoinPoolクラスは生成時に並行度を指定できますが、setParallelismメソッドを使うと後から変更することができます。

 

ForkJoinTaskクラス

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

  • static <T> ForkJoinTask<T> adaptInterruptible(Callable<? extends T> callable)
  • boolean quietlyJoin(long timeout, TimeUnit unit)
  • boolean quietlyJoinUninterruptibly(long timeout, TimeUnit unit)

adaptInterruptatibleメソッドはadaptメソッドの割り込み可能版です。タスクをcancelメソッドでキャンセルするときに、cancelメソッドの引数のmayInterruptIfRunningをtrueにしておくと、割り込みをかけてキャンセルしてくれます。

quietlyJoinメソッドのオーバーロードはタイムアウトを指定できるようになりました。引数無しのquietlyJoinメソッドは例外をスローしませんが、タイムアウトを指定できるようになったのでInterruptedException例外がスローされる可能性があります。

もう1つのquietlyJoinUninterruptiblyメソッドはquietlyJoinをする時に割り込みをかけないメソッドです。

 

ForkJoinWorkThreadクラス

ForkJoinWorkThreadクラスはFork/Join Frameworで使用するスレッドです。コンストラクタが1つ追加されました。

  • ForkJoinWorkerThread(ThreadGroup group, ForkJoinPool pool, boolean preserveThreadLocals)

今まで提供されていたコンストラクタはForkJoinPoolオブジェクトだけを指定するものでしたが、ThreadGroupとThreadLocalの有無を指定できるようになっています。このコンストラクタはVirtual Threadに関連してそうなんですけど、Previewではないようです。

 

Future.State列挙型

Futureインタフェースに状態を示す列挙型が追加されました。

定数として、以下の4種類が定義されています。

  • CANCELLED
  • FAILED
  • RUNNING
  • SUCCESS

というか、State列挙型のようなものが今までなかったのが不思議だったんですよね。

 

Futureインタフェース

Future.State列挙型の導入に伴い、それを使うメソッドが追加されました。

  • default Throwable exceptionNow()
  • default V resultNow()
  • default Future.State state()

stateメソッドは現在の状態を表すメソッドです。戻り値の方はもちろんFuture.State列挙型です。

resultNowメソッドは結果を取得するメソッドなのですが、getメソッドとは異なって、結果取得を待たないです。つまり、正しい値を取得するには状態がSUCCESSの場合だけです。

デフォルト実装では、その他の状態の場合、IllegalStateException例外がスローされるようになっています。

exceptionNowメソッドも同様に例外の取得を待たないメソッドです。今ままではgetメソッドのExecutionException例外のcauseで原因となる例外を取得していましたが、状態がFAILの場合はexceptionNowメソッドで例外を取得できるようになっています。

デフォルト実装では、その他の状態の場合、同じようにIllegalStateException例外がスローされます。

 

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

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

 

ToolProviderクラス

ToolProviderクラスはServiceLoaderクラスによって呼び出されるクラスなので、普通は使わないとは思いますが...

  • Optional<String> description()

ToolProviderオブジェクトの説明があれば返します。デフォルト実装ではOptional.empty()を返していました。

一応、jar, javac, jlinkの場合の説明がJavadocに記載されているので、興味があれば見てみてください。

 

 

というわけで、Java 19のJEPにのっていないAPIの変更でした。Java 19ではFFM APIやVirtual ThreadsなどのPreviewのAPI変更が多いのですが、意外にもそうでないAPIの変更も多めでした。個人的にはFuture.State列挙型の導入がうれしいですね。

PreviewやIncubatorもいつか紹介したいとは思ってはいます。

2022/03/22

JEPでは語れないJava 18

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

半年ぶりのJavaのアップデートの時間です。

Java 18はLTSの次のバージョンということもあり、JEPは少なめです。

  • 400: UTF-8 by Default
  • 408: Simple Web Server
  • 413: Code Snippets in Java API Documentation
  • 416: Reimplement Core Reflection with Method Handles
  • 417: Vector API (Third Incubator)
  • 418: Internet-Address Resolution SPI
  • 419: Foreign Function & Memory API (Second Incubator)
  • 420: Pattern Matching for switch (Second Preview)
  • 421: Deprecate Finalization for Removal

しかし、日本、特にWindowsの日本語環境で運用しているシステムにとって、とても重大な変更があります。

それが、JEP 400のUTF-8 by Defaultです。

Reader/Writer系のクラスで文字コードを指定しない場合、デフォルトの文字コードとしてUTF-8が使われるようになります。今まで、Windowsの日本語環境ではデフォルトの文字コードはWindows-31j (MS932)だったので、それを前提とした入出力は動作がおかしくなります。

もちろん、互換性維持のための策もちゃんとあって、file.encodingプロパティをCOMPAT、つまり実行時にオプションとして-Dfile.encoding=COMPATを指定すれば今まで通りのデフォルトの文字コードになります。

しかし、デフォルトの文字コードに依存したコードはなるべく早いうちに、文字コードを指定するコードに書き換えるべきです。

なお、NIO.2を使用した入出力はすでにデフォルトの文字コードがUTF-8になっているので、変更は必要ありません。

 

JEP 408は以前よりJDKに含まれていたWeb Server機能を使用できるツールの提供、JEP 413はJavadocにコードを書くために@snippetタグの導入です。

JEP 416はリフレクションの実装方法の改善です。JEP 421は以前より予告されていたfinalizerの廃止です。for Removalになったので、どんなに遅くなったとしても次のLTSではfinalizeメソッドは使えないと思った方がいいです。

他のIncubatorモジュールやPreviewは2ndや3rdなので、もうおなじみ?

 

例によってセキュリティ系のAPIの変更は櫻庭がよく理解していないので、省略します。

 

廃止になったAPI

メソッド

JEP 421関連でfinalizeメソッドが廃止になりました。これらはJava 16でfor Removalになっていたものです。

  • java.awt.color.ICC_Profile.finalize()
  • java.awt.image.ColorModel.finalize()
  • java.awt.image.IndexColorModel.finalize()

いずれもAWT関連なので、特に問題にはならないと思います。

 

廃止予定のAPI

Java 18で追加された廃止予定のAPIは、JEP 421関連のfinalizeメソッドがほとんどだけです。

 

メソッド

JEP 421関連のfinalizeメソッド以外のメソッドとして、とうとうThread.stop()メソッドがforRemovalになりました。stopメソッドを使われていることも少なくなっているとは思いますが、注意が必要です。

また、セキュリティ系の情報を保持するSubjectクラスのdoAsメソッドが廃止予定になりました。

  • java.awt.Graphics.finalize()
  • java.awt.PrintJob.finalize()
  • java.lang.Enum.finalize()
  • java.lang.Object.finalize()
  • java.lang.Runtime.runFinalization()
  • java.lang.System.runFinalization()
  • java.lang.Thread.stop()
  • java.util.concurrent.ThreadPoolExecutor.finalize()
  • javax.imageio.spi.ServiceRegistry.finalize()
  • javax.imageio.stream.FileCacheImageInputStream.finalize()
  • javax.imageio.stream.FileImageInputStream.finalize()
  • javax.imageio.stream.FileImageOutputStream.finalize()
  • javax.imageio.stream.ImageInputStreamImpl.finalize()
  • javax.imageio.stream.MemoryCacheImageInputStream.finalize()
  • javax.security.auth.Subject.doAs(Subject,PrivilegedAction)
  • javax.security.auth.Subject.doAs(Subject,PrivilegedExceptionAction)

 

追加されたAPI

LTSの次のバージョンということもあり、Java 18のAPIの追加はあまりありません。ただし、java.baseモジュールにメソッド追加が多いです。

 

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

java.ioパッケージでは2つのメソッドが追加されましたが、1つはスーパークラスで定義されていたものをオーバライドしたメソッドです。それがFileInputStreamクラスのtransferToメソッドです。

もう1つのメソッドがJEP 400に関連したメソッドです。

 

PrintStreamクラス

PrintSreamクラスでは現在使用している文字セットを返すcharsetメソッドが追加されました。

  • Charset charset()

Java 17でCosoleクラスにcharsetメソッドが追加されましたが、同じようにファイルに出力を行う文字セットを返します。

 

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

java.langパッケージではMathクラスとStrictMathクラスにメソッドが追加されました。いずれも同じメソッドなので、一緒に説明します。

Mathクラス/StrictMathクラス

Math/StrictMathクラスでは、それぞれ13のメソッドが追加されました。

  • int ceilDiv(int x, int y)
  • long ceilDiv(long x, int y)
  • long ceilDiv(long x, long y)
  • int ceilDivExact(int x, int y)
  • long ceilDivExact(long x, long y)
  • int ceilMod(int x, int y)
  • int ceilMod(long x, int y)
  • long ceilMod(long x, long y)
  • int divideExact(int x, int y)
  • long divideExact(long x, long y)
  • int floorDivExact(int x, int y)
  • long floorDivExact(long x, long y)
  • long unsignedMultiplyHigh(long x, long y)

Exactがつくメソッドは、ArithmeticException例外を投げるという違いだけなので、それ以外のメソッドを説明します。

ceilDiv系のメソッドは演算結果より大きいもしくは同じになる最小の整数を返します。

jshell> Math.ceilDiv(10,3)
$1 ==> 4

jshell> Math.ceilDiv(9, 3)
$2 ==> 3

jshell> Math.ceilDiv(-10, 3)
$3 ==> -3

ceilMod系の割り算の余りなのですが、負の値になることもあります。つまり、z = Math.ceil(x, y)だったときに、ceilModメソッドの返り値はx - z * yになります。

jshell> Math.ceilMod(10,3)
$1 ==> -2

jshell> Math.ceilMod(9, 3)
$2 ==> 0

jshell> Math.ceilMod(-10, 3)
$3 ==> -1

unsignedMultiplyHighメソッドはmultiplyHighメソッドの符号なし版です。

 

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

JEP 418でインターネットアドレスの名前解決がSPI化され、それに応じて4つのインタフェース/クラスが追加されました。

  • InetAddressResolverインタフェース
  • InetAddressResolver.LookupPolicyクラス
  • InetAddressResolverProviderインタフェース
  • InetAddressResolverProvider.Configurationクラス

これで独自の名前解決クラスを実装することができるようになります。

 

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

Charsetクラスにメソッドが1つ追加されています。

Charsetクラス

指定された文字セットを返すforNameメソッドがオーバーロードされました。

  • Charset forName(String charsetName, Charset fallback)

今までのforNameメソッドは指定された文字セットがない場合、IllegalCharsetNameException例外を投げます。これに対し、Java 18で追加されたforNameメソッドは文字セットがない場合、第2引数のfallbackが戻ります。

今までは文字セットがなかった場合は例外処理で処理しなくてはならなかったのが、fallbackを指定できるのでコードが分かりやすくなるはずです。

 

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

Durationクラスにメソッドが1つ追加されています。

Durationクラス

Durationクラスは時間間隔表すクラスですが、保持している時間間隔が正の値なのか調べるためのメソッドが追加されました。

  • boolean isPositive()

isZeroメソッドと、isNegativeメソッドはあったので、これで3つ揃いました。

 

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

HTTPリクエストを作成するBuilderクラスにメソッドが1つ追加されています。

HttpRequest.Builderクラス

HTTPリクエストを作成するBuilderクラスは、これまでHTTPメソッドのGET, POST, PUT, DELETEを作成するメソッドはあったのですが、HEADを作成するメソッドはありませんでした(HEADメソッドのリクエストを作ることはできます)。そこで、HEADメソッドが追加されています。

  • HttpRequest.Builder HEAD()

これまで、HEADメソッドはmethodメソッドで作らなくてはならなかったのが、少し簡単になりました。

 

java.xml/javax.xml.xpathパッケージ

なぜ、この時期にXPathにメソッドが追加されたのか全然わからないのですが、XPathのファクトリにメソッドが2つ追加されました。

XPathFactoryクラス

XPathFactoryクラスで属性を設定、取得するメソッドが追加されました。

  • String getProperty(String name)
  • void setProperty(String name, String value)

追加されたのはいいのですが、使い道がよく分かりません。デフォルトの実装ではUnsupportedOperationException例外が投げられるようになっています。

 

java.compilerモジュール

毎度のことですが、java.compilerモジュールはいろいろと追加されています。いつものソースバージョンやらなにやらが大半ですが、アノテーション処理関連でも少しだけメソッドが追加されています。とはいうものの、普通は使わないので省略します。

 

ということで、Java 18のAPIの変更をまとめました。

LTSの次のバージョンということで、変更点は少なめですね。

2021/09/15

JEPでは語れないJava 17

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

恒例の半年ぶりのblog更新です。

アメリカ西海岸時間の9月14日にJava SE 17がリリースされました。今回のJava 17は多くのディストリビュータがLTSにするバージョンなので、注目度も高いのではないでしょうか。

まぁ、常に新しいバージョンに移行している身にとってみては、LTSはあんまり関係ないんですけどねw

また、Oracle JDKが無償で使えるとか、LTSが2年周期になるとかいろいろ話題はあるようですが、ここでは触れないでおきます。

 

Java 17では17のJEPが導入されました。その中でPreviewがProject Amberによるパターンマッチング、IncubatorがProject Panama関連の2つがあります。

文法ではSealed ClassがPreviewが外されて、正式に導入されました。その他に注目すべきJEPとしてはMacのM1チップ用の移植があります。

その他は実装を改善したとか、Deprecatedを追加したとかが多いです。

 

廃止になったAPI

Java 15でDeprecatedになっていたRMI ActivationのAPIが削除されました。以下では、java.rmi.activationパッケージしか載せませんが、java.rmi.activationパッケージで定義されたクラスや例外も削除されています。

その他に、Java 16でforRemoval=trueになっていたURLDecoderのコンストラクタが削除されています。

 

パッケージ

  • java.rmi.activation

コンストラクタ

  • java.net.URLDecoder 

 

廃止予定のAPI

Java 17で追加された廃止予定のAPIには、JEP 398のApplet関連のAPIとJEP 411のセキュリティマネージャがあります。

セキュリティマネージャは、SecurityManagerクラス以外にも関連するクラスやメソッドが多くあるので、要注意です。とはいえ、SecurityManagerを使っている人を見たことがないので、そんな影響ないかなw

ちなみに、以下にはjava.appletパッケージのみ記載しますが、java.appletパッケージで定義されていたクラスも、もちろん廃止予定になります。

 

パッケージ

  • java.applet

クラス

  • java.bean.Thread.resume
  • java.beans.AppletInitializer
  • java.lang.SecurityManager
  • java.rmi.RMISecurityManager
  • java.security.AccessControlContext
  • java.security.AccessController
  • java.security.DomainCombiner
  • java.security.Policy
  • java.security.Policy.Parameters
  • java.security.PolicySpi
  • javax.security.auth.SubjectDomainCombiner
  • javax.swing.JApplet

例外

  • java.security.AccessControlException

メソッド

  • java.beans.Beans.instantiate
  • java.lang.SecurityManager.checkMulticast
  • java.lang.System.getSecurityManager
  • java.lang.System.setSecurityManager
  • java.lang.Thread.checkAccess
  • java.lang.ThreadGroup.checkAccess
  • java.util.concurrent.Executors.privilegedCallable
  • java.util.concurrent.Executors.privilegedCallableUsingCurrentClassLoader
  • java.util.concurrent.Executors.privilegedThreadFactory
  • java.util.logging.LogManager.checkAccess
  • javax.security.auth.Subject.doAsPrivileged(Subject,PrivilegedAction,AccessControlContext)
  • javax.security.auth.Subject.doAsPrivileged (Subject,PrivilegedExceptionAction,AccessControlContext)
  • javax.security.auth.Subject.getSubject
  • javax.swing.RepaintManager.addDirtyRegion

フィールド

  • javax.naming.Context.APPLET

 

追加されたAPI

なんだかよくわからないのですが、Java 17ではAPI大量に追加されてます。しかも、java.ioパッケージとかjava.util.concurrentパッケージなどよく使うパッケージでAPIが追加されています。

また、JEP 356で新しい乱数発生器が追加されています。なんと、java.util.randomというパッケージが追加されました。

 

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

java.ioパッケージではメソッドの追加が11もありますが、ほとんどがスーパークラスで定義されていたものをオーバライドしたメソッドです。たとえば、CharArrayReaderクラスのread(CharBuffer)メソッドや、FileInputStreamクラスのreadAllBytesメソッドなどがこれに相当します。

 

Consoleクラス

次のJava 18で導入予定のJEP 400で、入出力の文字コードのデフォルトがUTF-8で統一されるのですが、それに先駆けてなのかcharsetというメソッドが追加されました。

  • Charset charset()

このcharsetメソッドはConsoleクラスで使用している文字セットを返します。Windowsで実行するとWindows-31jが返り、Ubuntuで実行するとUTF-8が返ります。

ObjectInputFilterインタフェース

オブジェクトのデシリアライズをする時にチェックをするための使われていたObjectInputFilterインタフェースですが、staticファクトリメソッドが追加されました。

今までは、ObjectInputFiler.Configクラスにファクトリメソッドがあったのですが、それをインタフェース側に持ってきたような感じです。使い方はずいぶん違いますけど。

  • ObjectInputFilter allowFilter(Predicate<Class<?>> predicate, ObjectInputFilter.Status otherStatus)
  • ObjectInputFilter removeFilter(Predicate<Class<?>> predicate, ObjectInputFilter.Status otherStatus)
  • ObjectInputFilter merge(ObjectInputFilter filter, ObjectInputFilter anotherFilter)
  • rejectUndecidedClass(ObjectInputFilter filter)

allowFIlterメソッドは、条件に合致すれば許可を行うフィルターを作成するメソッドです。第2引数のStatus列挙型は第1引数のラムダ式がfalseを返した時に適用されます。

ちなみに、このファクトリで作成されたフィルタのcheckInputメソッドをコールされた時、引数のFilterInfoオブジェクトのserialClassメソッドの返り値がnullだとUNDEFINEDを返します。

removeFIlterメソッドはallowFilterメソッドの逆。

mergeメソッドはフィルタを統合するメソッドで、rejectUndecidedClassメソッドは引数のフィルタがUNDEFINEDを返すような場合にREJECTEDを返すようなフィルタを作成します。

ObjectInputFilter.Configクラス

ObjectInputFilterインタフェースにファクトリメソッドが追加されましたが、Configクラスにはファクトリメソッドのgetterとsetterメソッドが追加されました。システム全体のデフォルトのフィルタに使用されました。

  • static BinaryOperator<ObjectInputFIlter> getSerialFilterFactory()
  • static void setSerialFilterFactory(BinaryOperator<ObjectInputFilter> filterFactory)

 

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

java.langパッケージでは変更のあったのは、Processクラスだけです。

Processクラス

ProcessクラスはProcessBuilderクラスで作成されたプロセスの制御を行うクラスです。

Java 17で追加されたのは起動されたプロセスの標準入出力に接続するためのメソッドです。

  • BufferedReader inputReader()
  • BufferedReader inputReader(Charset charset)
  • BufferedReader errorReader()
  • BufferedReader errorReader(Charset charset)
  • BufferedWriter outputWriter()
  • BufferedWriter outputWriter(Charset charset)

inputReaderメソッドがプロセスの標準出力を受け取るためのリーダーを取得するメソッドです。同様にerrorReaderメソッドがエラー出力、outputWriterメソッドが標準入力になります。

 

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

java.lang.invokeパッケージではラムダ式の実行で使われるAPIを定義しているので、普段使うことはほとんどないはず。

MethodHandlesクラス

MethodHandleクラス用のユーティリティクラスであるMethodHandlesクラスではメソッドが1つ追加されました。switch文で使われることを想定した、複数のMethodHandleオブジェクトを指定して実行する用途のMethodHandleオブジェクトを作成できます。

  • static MethodHandle tableSwitch(MethodHandle fallback, MethodHandle... targets)

まぁ、使うことはないかなw

 

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

Java 16で追加されたラムダ式のブートストラップを定義するjava.lang.runtimeパッケージに、クラスが1つ追加されました。

SwitchBootstrapクラス

switch式でindyを使えるようにするためのブートストラップを提供するクラスです。ただしPreview機能です。

メソッドはブートストラップとなるtyepSwitchメソッドだけです。

  • static CallSite typeSwitch(MethodHandles.Lookup lookup, String invocationName, MethodType invocationType, Object... labels)

これも、使うことはないかなw

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

UDPで通信を行うDatagramSocketクラスにメソッドが追加されました。

DatagramSocketクラス

マルチキャストのグループに関するメソッドが2つ追加されました。

  • void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf) throws IOException
  • void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf) throws IOException

今までは、明示的にグループを扱えなかったので、マルチキャストはやりやすくなるはず。

 

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

MappedByteBufferクラスにメソッドが追加されました。というか、なんで今までなかったんだろう?

MappedByteBufferクラス

compactメソッド、duplicateメソッド、sliceメソッドが追加されていますが、いずれもByteBufferクラスのオーバーライドになので、説明は省略します。

 

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

Date & Time APIで使用されるjava.timeパッケージに新しいインタフェースが追加されました。

InstantSourceインタフェース

InstantSourceインタフェースで定義するメソッドは7つ。そのうちstaticファクトリメソッドが4種類、デフォルトメソッドが2種類。つまり、実装クラスで定義しなくてはいけないのメソッドは1つです。

  • Instant instant()
  • default long millis()
  • default Clock withZone(ZoneId zone)
  • static InstantSource fixed(Instant fixedInstatnt)
  • static InstantSource offset(InstantSource baseSource, Duration offsetDuration)
  • static InstantSource system()
  • static InstantSource tick(InstantSource baseSource, Duration tickDuration)

instantメソッドがInstantオブジェクトを返すメソッドで、これを実装する必要があります。

millisメソッドはSystem.currentTimeMillisメソッドと同等のメソッドです。エポック秒を返します。withZoneメソッドはInstantオブジェクトに基づいて、引数のタイムゾーン用のClockオブジェクトを返すメソッドです。

staticファクトリメソッドのfixedメソッドは常に同じ値を返すInstantオブジェクトを生成するInstantSourceオブジェクトを生成するためのメソッドです。まぁ、デバッグ用途ですかね。

systemメソッドはSystem.currentTimeMillisメソッドをソースとしたInstantSourceオブジェクトを返すメソッドです。

offsetメソッドと第2引数で指定された期間をオフセットとするためのファクトリメソッドです。

tickメソッドも引数は同じなのですが、動作はちょっと違います。第2引数で指定された期間は同じ値を返すInstantオブジェクトを生成するためのファクトリメソッドです。といっても、よく分からないと思うので、JShellで確かめてみます。

以下ではDurationとして2秒を指定しています。

jshell> var system = InstantSource.system()
system ==> SystemInstantSource

jshell> var duration = Duration.ofSeconds(2)
duration ==> PT2S

jshell> var instantSource = InstantSource.tick(system, duration)
instantSource ==> TickClock[SystemClock[Z],PT2S]

jshell> instantSource.instant().toEpochMilli()
$41 ==> 1631677098000

jshell> instantSource.instant().toEpochMilli()
$42 ==> 1631677100000

jshell> instantSource.instant().toEpochMilli()
$43 ==> 1631677102000

jshell> instantSource.instant().toEpochMilli()
$44 ==> 1631677102000

jshell> instantSource.instant().toEpochMilli()
$45 ==> 1631677104000

jshell> instantSource.instant().toEpochMilli()
$46 ==> 1631677104000

jshell> instantSource.instant().toEpochMilli()
$47 ==> 1631677106000

jshell> instantSource.instant().toEpochMilli()
$48 ==> 1631677106000

jshell> instantSource.instant().toEpochMilli()
$49 ==> 1631677108000

生成したInstantオブジェクトのtoEpochMillisメソッドは繰り返しコールしているのですが、奇数秒は返ってきません。偶数秒になっているのはたまたまですが、2秒ごとに時間が進んでいるのが分かるはずです。

 

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

java.utilパッケージには16進数用のフォーマッタクラスが追加されました。でも、なぜjava.textでなくてjava.utilなんだろう?

HexFormatクラス

HexFormatクラスは前述したように、16進数用のフォーマッタです。メソッドが36個もあるので、ここで全部を紹介すのはつらい...

of系のファクトリクラスが2種類で、通常はこれらを使用してHexFormatオブジェクトを生成します。また、with系のファクトリメソッドも5種類あります。

フォーマットを行うformatHexメソッドが4種類のオーバーロードがあります。これらは、バイト配列をフォーマットします。バイト配列ではなく、単一の数値であればtoHexDigitsメソッドを使用します。

逆に、文字列をバイト配列にフォーマットするには、parseHexメソッドを使用します。こちらも3種類のオーバーロードがあります。

また、staticメソッドであるfromHexDigitsメソッドもあります。こちらは文字か文字列をint型の値に変換します。

ちょっと使ってみましょう。

jshell> var format = HexFormat.of()
format ==> uppercase: false, delimiter: "", prefix: "", suffix: ""

jshell> format.toHexDigits(200)
$2 ==> "000000c8"

jshell> format.formatHex(new byte[]{0, 5, 10, 15, 20, 25})
$3 ==> "00050a0f1419"

jshell> var format2 = HexFormat.ofDelimiter(",")
format ==> uppercase: false, delimiter: ",", prefix: "", suffix: ""

jshell> format2.formatHex(new byte[]{ 0, 5, 10, 15, 20, 25})
$5 ==> "00,05,0a,0f,14,19"

ofメソッドで生成したHexFormatオブジェクトはバイト配列をフォーマットすると、すべて詰めて文字列にしてしまいます。カンマ区切りや空白区切りにしたい場合は、ofDelimiterメソッドを使用してHexFormatオブジェクトを生成します。

Map.Entryインタフェース

Map.Entryインタフェースにstaticメソッドのコピーメソッドが追加されました。

  • static <K, V> Map.Entry<K, V> copyOf(Map.Entry<? extends K, ? extends V> e)

コピーしたエントリーはどのマップとも結びついていない状態になります。

SplittableRandomクラス

SplitableRandomクラスは、新しく追加された乱数発生器のインタフェースを実装するように実装が変更されています。実装しているインタフェースは以下の3種類です。

  • RandomGenerator
  • RandomGenerator.SplittableGenerator
  • RandomGenerator.StreamableGenerator

また、以下のメソッドが追加されました。

  • SplittableRandom split(RandomGenerator.SplittableGenerator source)
  • Stream<SplittableRandom> splits()
  • Stream<SplittableRandom> splits(long streamSize)
  • Stream<SplittableRandom> splits(RandomGenerator.SplittableGenerator source)
  • Stream<SplittableRandom> splits(long streamSize, RandomGenerator.SplittableGenerator source)

splitメソッドやsplitsメソッドはストリーム処理の中で使われるので、一般には使わないと思います。

 

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

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

ThreadLocalRandomクラス

nextFloatメソッドのオーバーロード2つが追加されています。いずれも、nextDoubleメソッドではできていた下限、上限値を設定できるようになっています。

  • float nextFloat(float bound)
  • float nextFloat(float origin, float bound)

boundが上限値(この値は含まない)、originが下限値(この値は含む)です。

 

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

JEP 356に基づいて、新しい乱数のパッケージができました。

詳細はいつか書きます。

 

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

SourceVersion列挙型

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

  • RELEASE_17

 

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

javax.lang.model.utilパッケージではコンパイル時に使用するプログラム要素の扱うためのビジターなどをいろいろと定義しているパッケージです。

Elementsインタフェース

プログラムの要素に対する操作を行うインタフェースですが、Automatic Moduleのチェックメソッドが追加されました。

  • default boolean isAutomaticModule(ModuleElement module)

 

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

まさかのSwingに変更がありました。ちょっとビックリw

JSlider.AccessibleJSliderクラス

Swingでスライダーを表すJSliderクラスのアクセシビリティサポート用のクラスがAccessbleJSliderクラスです。今まで実装していたインタフェースに加えてAccessibleActionインタフェースを実装するようになりました。

そのため、AccessibleActionインタフェースの3つのメソッドが追加されています。

  • boolean doAccessibleAction(int i)
  • int getAccessibleActionCount()
  • String getAccessibleActionDescription(int i)

他のSwingコンポーネントはAccessibleActionインタフェースを実装しているので、なぜ今になってJSliderクラスに加わったのかはちょっと謎。

 

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

ファイルチューザーが使用するファイルシステムのファサード的なクラスであるFileSystemViewクラスにメソッドが追加されています。

FileSystemViewクラス

ファイルチューザーで表示されるシステムアイコンを取得するためのメソッドgetSystemIconメソッドがオーバーロードされています。

  • Icon getSystemIcon(File f, int width, int height)

今までのgetSystemIconメソッドとの違いはアイコンのサイズを指定できるようになったことです。

 

これ以外にjava.xml.cryptoモジュールでRSA-PSSがサポートされました。

ということで、Java 17のAPIの変更をまとめました。

JEPに絡むところはjava.util.randomパッケージですね。それ以外にもちょこちょこと変更があります。とは言え、すぐに使えそうなクラスはHexFormatクラスぐらいでしょうか。

Project Panama関連のIncubator Moduleについては解説しませんでしたが、これはこれでおもしろいので、別エントリーでやるかもしれません。

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に移行するのも楽かもしれませんね。