2024/03/17

Jfokus 2024 その2 セッション編

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

前回に引き続き、Jfokusの参加記です。

www.javainthebox.com

 前回はJfokusに参加するまでの話ですが、本エントリーではさくらばがJfokusに参加して興味深かったセッションを紹介します。


Jfokusは3日間の会期中、1日目がチュートリアルとハンズオンが行われます。2, 3日目が通常のセッションです。


Java 21 Deep Dive - Better Language, Better Scalability, Better APIs, Better Tools

チュートリアルで聴講したのがこれ。おなじみのOracleのアドボケイトのNicolai ParlogとAna-Maria Mihalceanuのセッションです。

資料はこちら。

slides.nipafx.dev


Pattern Matching、Virtual Threads、String Templatesが前半で、後半はSequenced Collectinosなど細かな機能、最後にツール系を紹介していました。

まぁ、さくらばには、ほとんどが知っていることだったので、機能の再確認をしたという感じです。

適度にまとまっているので、機能のチェックをしたいのであれば、ちょうどいいと思います。


Java in 2024

Jfokusのキーノートセッションで、こちらもおなじみ、OracleのGeorges Saabです。


当初は、JavaのチーフアーキテクトのMark Reinholdが話す予定でした。しかし、Markの来訪がキャンセルになってしまって、急遽Georgesになりました。

さくらばはMarkのセッションを楽しみにしていたので、キャンセルと聞いてモチベーションダダ下がり。さらにVM Tech Summitもなくて、さらにモチベーションが下がる。

そのモチベーションの低さが会場の写真などをほとんど撮らなかったことにつながるわけです。

Georgesの話はいつも通りな感じですね。


Java Language Update

Devoxx BEでBrian Goetzが話したセッションのアップデート版。JfokusではOracleのViktor Klangが担当。


最近、ViktorさんとParさんが話すことが多いようなんですけど、そういう役割なんですかね。

Java 21だけでなく、Java 17ぐらいからのJava言語仕様の変化についてまとめたセッションです。


Enter the Parallel Universe of the Vector API

AzuleのSimon RitterのVector APIに関するセッション。


資料はこちら。

Enter The Parallel Universe of the Vector API


Vector APIについての分かりやすい解説。これを見ておけば、だいたい理解できるんじゃないかなぁ。資料だけだとちょっとつらいかもしれないですが。

Java 22でFFMが正式に導入されるので、Vector APIももうすぐですね。


Modern Java in Action

こちらもNicolai Parlogのセッション。


資料はこちら。

slides.nipafx.dev


GitHubをクロールしてするサンプルアプリケーションを古いスタイルから新しいスタイルに書き換えていくというライブコーディングのセッション。

なかなかおもしろいけど、早すぎて途中からついていけないのが...


これ以外にも、Ubertoの関数型のセッションや、ParさんのLeydenのセッション、Datadogのプロファイラー、AlinaとShaunのGraalVMなどを聴講しました。

それにしても、VM Tech Summitがなかったのがイタイ。

来年は、VM Tech Summitがあれば参加するつもりですが、ないのならばやめようかなぁと思うさくらばなのでした。

2024/03/16

Jfokus 2024 その1 準備編

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

2月5から6日にかけて、スウェーデンのストックホルムでJavaのカンファレンスのJfokus 204が開催されました。今回、日本人の参加者は私を含めて3人しかいませんでした。

ぜひ、来年は日本からの参加者が増えるといいなぁということで、Jfokusの備忘録です。

なお、2月3, 4日にはベルギーのブリュッセルでFOSDEMというカンファレンスも開催されています。FOSDEMとJfokusの両方とも参加される方も多いのですが、さくらばはJfokusだけ参加しました。

今回はあまり写真を撮っていないので、会場などの写真はほぼないです。すみません。


Jfokus

Jfokusはスウェーデンのストックホルムで2007年から開催されているJavaのカンファレンスです。VM Tech SummitというVMに特化したイベントも一緒に行っているなど、ちょっとデープなカンファレンスになっています。

しかし、コロナ後はVM Tech Summitが開催されておらず、普通の大規模なJavaカンファレンスになっていました。今年は、事前にはVM Tech Summitもあると言われていたのですが、結局なかったらしいです。かなり残念。

www.jfokus.se

参加者は1,000人ぐらい?上述したようにFOSDEMから参加している方も多いようです。

スウェーデンでの開催ですが、セッションは英語です。スウェーデンはTOEICの国別ランキングで常に上位にいる国なので、どこでも英語でOKのようです。


チケット

JfokusはDevoxxのようにチケットの争奪戦になることはないですが、売り切れることもあるので早めに取得するのがいいと思います。また、Devoxxとは異なり、クレジットカードに対応しています。


ストックホルムへ

Jfokusに参加することが決まったら、まずやることは交通手段と宿泊の確保です。


空路

ストックホルムはアーランダ空港とスカブスタ空港がありますが、ほとんどがアーランダ空港になると思います。ただし、ライアンエアーなどの航空会社はスカブスタ空港をしていますが、まぁ使うことはないと思います。

現状、アーランダ空港への直行便はないので、どこかしらで乗り継ぎを行う必要があります。

スウェーデンはシェンゲン協定加盟国なので、フランクフルトやパリから乗り継ぐ場合は乗り継ぐ場所で入国審査を受ける必要があります。このため、乗り継ぎには余裕をみてスケジュールしたほうがいいです。

イギリスなどシェンゲン協定に加盟していない国はスウェーデンで入国審査を受けます。

今回、さくらばはロンドン ヒースロー空港で乗り継ぎで、アーランダ空港着でした。


ストックホルム市内へ

アーランダ空港からストックホルム市内へは、鉄道のアーランダエクスプレスを使うのが便利です。

ターミナル5に直結したアーランダ北駅と、ターミナル2, 3, 4から利用できるアーランダ南駅とストックホルム中央駅を結ぶ鉄道で、だいたい40分ぐらいでストックホルム中央駅に着きます。

チケットは空港でも購入できますが、事前にオンラインで購入する方がいいと思います。オンラインで購入すると、QRコードが発行されるので、それを駅でスキャンすればOKです。

列車内で検察にくることもあります。

www.arlandaexpress.com


宿泊

Jfokusの会場はストックホルム中央駅に直結したStockholm Waterfront Congress Centreという場所で開催されます。

なので、中央駅近辺でホテルを探すのがいいと思います。

一番いいのは会場と同じ建物にあるRadisson Blu Waterfront Hotelですが、周りのホテルに比べると宿泊費は高めです。なお、すぐそばに同じ系列のRadisson Blue Royal Vikingというホテルもあるので、お間違えなく。

Radisson Blue Waterfront Hotelは新しい建物なのですが、中央駅の近辺は古い建物が多いのでホテルも古いところが多い感じです。ちょっと離れると新しいところもあるので、会場まで近いという利便性をとるか、施設のよさをとるかのどちらかですね。

今回、さくらばはFreys Hotelに宿泊しましたが、ここもかなり古い建物でした。


気候

北欧と聞いたらやっぱり寒いと思いますよね。

今年のJfokus会期中は一番寒い日で最低気温-10度、最高気温-3度ぐらいでした。

Jfokusの前の週がかなり暖かくて、前々週は最低気温が-20度になるぐらいの寒さだったようです。

したがって、行くとしたら、最低気温が-20度になっても耐えられるぐらいの服装で!今年もそこまで寒いというわけではなかったですが、日本からの参加者の1人が寒さで風邪をひいてしまっていました。せっかくカンファレンスに参加するのですから、体調を崩さないように、万全の体制で挑むようにしましょう。

前週が暖かったということから、今年は街中はほとんど雪は残ってませんでした。昨年は雪も残っていて、道がアイスバーン化しているところもあったらしいので、靴もそれ用に用意したほうがいいと思います。

ストックホルムは湖に面した街ですが、最低気温が-10度にもなると湖も運河もカチカチに凍りますね。


Jfokus会場

Jfokusの会場は前述したようにWaterfront Congress Centreです。ストックホルム中央駅からほぼ直結してます。

ただ駅の裏側なので、ちょっと分かりにくいかもしれません。

古い建物が並ぶストックホルムの中心街の中で、ここだけは妙にモダンな建物になっています。

運河に面している建物ですが、運河の向かい側は市庁舎です。私は知らなかったのですが、魔女の宅急便に出てくる時計台の尖塔は、この市庁舎がモデルらしいです。

入り口にはクロークがあるので、上着はここで預けられます。会場は暖かく、上着は邪魔になるだけなので、預けた方がいいと思います。


食事

Jfokusは、朝食とランチが提供されます。

朝食にはスウェーデン名物でもあるカネルブッレ(シナモンロール)やカルダモンマブッレ(カルダモンロール)、オープンサンドのスモーブローなどが出されます。

昼は日によって異なりますが、スウェーデン料理のプレート。けっこうおいしいです。

また、会期2日目の夜は展示会場でレセプションがあり、軽食がでます。ただ、おなかがいっぱいになるほどではないですね。

気をつけなくてはいけないのが、レストランが意外と早い時間にしまってしまうこと。行くところが決まっているのであれば、予約してからいくのがいいようです。

逆に早朝からやっているカフェやイートイン併設のパン屋さんは多いんですけどね。


後編では、さくらばがJfokusで聴講したセッションの中から面白かったものについて紹介します。

2024/02/02

なぜあなたはラムダ式が苦手と感じるのか

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

今年もブリ会議で講演してきました。例年、Javaの新しい機能などについて話すことが多かったのですが、今年はProject Lambdaから10年ということもあり、あらためてラムダ式についての話です。

2部構成で前半が初心者向けの「なぜあなたはラムダ式を苦手と感じるのか」というラムダ式を使う立場での話。後半はぐっと難易度があがって、「ラムダ式はどうやって動くのか」という内容です。

このエントリーはブリ会議で話した内容の前半部分の解説です。資料はこちら。


ラムダ式が導入されたのは2014年、Java 8の時です。今年でちょうど10年ですね。

10年もたったのですから、ラムダ式を当たり前のように使っている開発者も多いとは思いますが、いまだに苦手と感じている方がいらっしゃるのも事実だと思います。

ラムダ式が導入後にJavaを使い始めた方でも苦手という方がいるんですよね。

ラムダ式は、Javaで関数型プログラミングの考え方が導入された端緒です。ラムダ式と一緒に導入されたStream APIをはじめ、今では関数型プログラミングに関する機能がJavaではどんどん増えています。

たとえば、言語仕様では

  • switch式
  • Pattern
  • Sealed Class
  • パターンマッチング

など。PatternとSealed Classは、両方を組み合わせて代数的データ型 (Algebraic Data Type, ADT) を構成するのに使われます。

標準のAPIだと、次のようなAPIがあります。

  • Stream API
  • Flow (Reactive Stream)
  • HTTP Client

一番使われているのはもちろんStream APIですね。HTTP Clientは、Java 11で導入されましたが、Flowを使用して宣言的にHTTPのクライアントを記述します。

標準API以外でも、Spring WebFluxや、Oracle Helidon、Red HataのRed Hat Quarkusなど関数型プログラミングの考えを使うライブラリやフレームワークも増えています。

つまり、これからはJavaを使っていても関数型プログラミングからは逃れられなくなっているのです。

関数型プログラミングの考えを取り入れたプログラミングスタイル、つまり今までの手続き的な記述から宣言的な記述に変えていかなくてはなりません。

さらにいうと、今までのJavaはどうしても文で考えがちなのですが、そうではなく式を使って記述することを意識していきたいのです。


ラムダ式とは

さて、そのラムダ式ですが、端的にいえば関数です。名前はないので、無名関数ということができます。

無名関数といっても、メソッドと同じようなものです。Java Language Specificationのラムダ式の説明の一番はじめには、次のような記述があります。

A lambda expression is like a method: it provides a list of formal parameters and a body - an expression or block - expressed in terms of those parameters. (JLS 15.27)

つまり、メソッドが書ければラムダ式は書けるはずなのです。

もちろん、ラムダ式は通常のメソッドとは異なり、クラスに属していなかったり、ラムダ式の外側のクラスの状態にアクセスすることも制限されていたりするので、メソッドと同じというわけではありません。

しかし、それは今までの手続き的な記述にとらわれてしまっているからではないでしょうか。


手続き的な記述の欠点

手続き的な記述には読みにくいポイントや、バグを発生しやすくしてしまうポイントがあります。

代表的なポイントを以下に示しました。


1点目の複数の処理を一緒に書けてしまうというのは、処理を十分に分解せずにまとめて書きがちだということです。

残りの2点は変数に関することです。手続き的な記述だと、どうしてもy処理の途中経過を保持するなどの変数が使われます。途中経過を保持させるので、ミュータブルになってしまうわけです。

また、途中経過を保持させて後に、最終的な結果を得るためには別のスコープに入ることがあり、スコープが広くなってしまいがちです。

言葉で書いても分かりにくいと思うので、具体的なコードで見てみましょう。

たとえば、A組の生徒の平均値を算出することを考えてみます。

生徒の成績は次のRecordで保持しているとします(通常のクラスでもいいのですが、こういうデータを保持させるにはイミュータブルなRecordの方が適しています)。

    record StudentScore(
            String name,
            String className,
            int score) {}

StudentScoreでは生徒の名前と、クラス、成績を保持させています。

平均値を求めるメソッドを次に示します。

    double calcAverage(List<StudentScore> scores) {
        double sum = 0.0;
        int count = 0;
 
        for (int i = 0; i < scores.size(); i++) {
            var ss = scores.get(i);
            if (ss.className().equals("A")) {
                sum += ss.score();
                count++;
            }
        }

        sum /= count;

        return sum;
    }

一見、よさげに見えますが、何が問題なのでしょう。

まず、ローカル変数のsumとcountです。この2つの変数はいずれもループでの中間値を保持させるためにミュータブルになっています。

また、ループが終わった後の最終的な結果を求めるために、ループの外側でも変数を使用します。このため、変数のスコープがメソッド全体になってしまっています。

ミュータブルだと意図しない値の変更がされる可能性があります。このメソッドは行数が少ないからよいですが、行数が多いメソッドを複数人で編集していたりすると意図しない変更が起こりがちです。

スコープが広い変数だとなおさらです。

たとえば、このメソッドでは最後にsumをcountで割って平均値を求めていますが、変数sumはループの中では合計値を保持させています。合計値を保持させる変数であるのに、最後に合計値ではない値を代入しているわけです。

平均値を代入した後に、他の人が合計値だと思って変数sumを使ってしまうとバグが発生してしまいます。


そして、ループです。

ループというか、繰り返し処理ってやっぱり分かりにくいと思うんですよ。

慣れてしまえばパターンとして覚えてしまうので、パッと書けるとは思いますが、初心者には難しい。

ここでのたった7行のループで、ループの制御、値の取り出し、比較、合計処理、カウンターのインクリメントまでやっています。特にループカウンターを使ったループの制御は本来の平均を求める処理とは別個のものなので、一緒にしてしまうと分かりにくくなってしまいます。

ここでは普通のfor文で書きましたが、for-each (拡張for文)で書いたとしても本質的な難しさは変わりません。

ループの制御はコンテナ側に任せ、ループで扱うデータに対し何を行っていくのかを分解して考えることで分かりやすさ、読みやすさは格段に向上します。

これが宣言的に記述するということにつながります。

for文という文ではなく、式で処理を連ねていくわけです。


宣言的な記述

先ほどの平均値を求めるメソッドを宣言的に記述したのが以下のコードです。

    double calcAverage(List<StudentScore> scores) {
        final var ave = scores.stream()
                              .filter(ss -> ss.className().equals("A"))
                              .collect(Collectors.averagingDouble(ss -> ss.score()));
        
        return ave;
    }

この記述には、return以外は文が使われておらず、式で処理を記述しています。

そして、ローカル変数のaveはイミュータブルな変数になります。

Stream APIを使ったループはいわゆる内部イテレータになり、ループの制御はStream APIが行います。Stream APIを使う側は、データをどのように処理するかだけに集中し、それをラムダ式で記述します。

行っている処理自体は手続き的に記述しても、宣言的に記述しても同じです。

しかし、Stream APIを使うことで、条件、値の取り出しなどの処理をそれぞれ1つのラムダ式で記述することで、処理の流れが分かりやすくなります。

とはいっても、今まで手続き的な記述しかしてこなかった方には、宣言的な記述はとっつきにくい感じを受けてしまうのはしかたないと思います。

だからといって、今までの手続き的な記述に固執していたら、どんどん増えている宣言的スタイルのライブラリやフレームワークを使えなくなってしまいます。

いつかは宣言的な考え方をしなくてはいけないのであれば、今がそのチャンスです。


重要なのはシグネチャー

前述したように、ラムダ式は関数として扱うのが自然です。

しかし、関数型インタフェースがとかを考え始めてしまうと、なかなかとっつきにくくなります。

Javaにも関数型があればこんなことに悩む必要はないのですが、残念ながらJavaでは関数型はありません。しかたないので、関数型インタフェースなんてものを持ち出したわけです。

しかし、ラムダ式を関数と考えるのであれば、型はそれほど重要ではありません。

重要なのはシグネチャーです。つまり

  • 引数の個数と、その型
  • 戻り値の有無と、その型

が重要になります。引数や戻り値の型はジェネリクスの型パラメータで定義されるので、引数の個数や戻り値の有無から考えましょうということです。

たとえば、数値を保持しているリストをソートするには以下のように記述します(もっと簡単に書けますけど、説明のためこう書いてます)。

    List<Integer> nums = ...;

    var sortedNums = nums.stream()
                         .sorted((x1, x2) -> x1 - x2)
                         .toList();

sortedメソッドの引数のラムダ式で要素同士を比較して、ソートの並び順を決めています。

このラムダ式はComparator<S>インタフェースなのですが、実際にはBiFunction<S, S,  Integer>インタフェースだとしても、処理はまったく同じです。

つまり、sortedメソッドの引数にするラムダ式では2つの引数が渡されて、戻り値としてintで戻すということが重要になるわけです。


java.util.functionパッケージのインタフェース

ラムダ式は関数で、重要なのはシグネチャということですが、ではFunctionインタフェースなどのjava.util.functionパッケージで提供されている関数型インタフェースはどのように考えればよいのでしょう。

インタフェース自体の用途などは気にせずに、シグネチャーを区別するためのものと割り切ると理解しやすいです。

つまり、シグネチャーが

  • 引数なし、戻り値ありならば Supplier<T>
  • 引数が1つ、戻り値ありならば Function<T, R>
  • 引数が1つ、戻り値がbooleanならば Predicate<T>
  • 引数が1つ、戻り値はなしならば Consumer<T>

のように考えるわけです。

たとえば、Streamインタフェースのmapメソッドの引数はFunctionインタフェースですが、その場合は引数が1つで、戻り値ありのラムダ式を書けばいいのだなと分かるわけです。


ちなみに、ラムダ式を関数型インタフェースの匿名クラスの延長として考えてしまうのは危険です。それだと、いつまでたっても手続き的な考え方に執着してしまいます(実際に動作としても匿名クラスとラムダ式はまったく異なるのですが、それはブリ会議の後半のエントリーで説明します)。

そんな変なことを考えずに、ラムダ式は関数として考えましょう。そして、ラムダ式を書く時にはシグネチャーから処理を記述していきましょう。


ラムダ式を書く時のTips

ここまでラムダ式は関数として扱いましょうということを説明してきたわけですが、では実際にラムダ式を書く時にどうすればよいでしょう。

ラムダ式は単独で使うということはほぼなく、ほとんどがライブラリやフレームワークのメソッドの引数として使います。

ということはラムダ式を単体で考えるのではなく、ライブラリやフレームワークと合わせて一緒に考えればよいということです。

ちなみに、メソッドの引数や戻り値に関数を使用することを高階関数と呼びます。名前はどうでもいいのですが、メソッドの引数にラムダ式というのがラムダ式の主流の使い方ということです。

Project LambdaのスペックリードのBrian GoetzはJava Magazineのインタビューで次のように語っています。

Project Lambdaは単なる言語機能ではなく、ライブラリも対象としています。言語機能とライブラリが一体となって、Javaプログラミング・モデルを大幅にアップグレードします。 (Project Lambdaの展望, Java Magazine Oct. 2012)

ラムダ式を策定したProject Lambdaではラムダ式とStream APIを合わせて策定しています。

このようにラムダ式単独ではなく、ライブラリやフレームワークと一緒に使うことを前提にラムダ式を考えていきましょう。


次の処理は1つだけというのは、ラムダ式はなるべくシンプルにしましょうということです。ラムダ式のボディにいろいろ処理を書いてしまうというのは、処理を正しく分解できていないということにつながり、どうしてもラムダ式のボディが手続き的になってしまいます。

処理を分解して、1つのラムダ式には単純な処理を記述し、それを連ねていくというスタイルに変えていきましょう。


3つめはラムダ式で扱うデータをなるべく引数だけにするということです。

ラムダ式は、ラムダ式が定義されている外側のクラスのフィールドなどにもアクセスできますが、それは可読性の低下につながります。もし、外部のデータにアクセスするのであれば、定数などに限定しましょう。


4つめの処理結果を戻り値以外で戻さないというのは、3つめの外部のデータにアクセスしないにも通じます。関数なのですから、結果は戻り値だけです。


最後はラムダ式だけでなく、宣言的な記述全般にいえることですが、変数はイミュータブルにしましょう。


まとめ

さて、前半のまとめです。

なんども書いていますが、ラムダ式は関数としてあつかい、宣言的な記述を行うために役立てるのがお勧めです。

匿名クラスの延長として考えてしまうと、今までの手続き的な考えの枠内にとどまってしまい、宣言的な記述を書くことの妨げになってしまいます。

Javaは今までの手続き的な記述から、宣言的な記述にプログラミングスタイルが変わってきています。今後もこの流れは変わりません。

ですから、手続き的な考えでラムダ式を理解しようとすることはやめましょう。

宣言的な記述、文から式への移行を役立てるためにラムダ式が存在するのです。


最後に参考文献にあげた「なっとく! 関数型プログラミング」を紹介しておきます。

この本はJavaからScalaへの移行を解説した書籍です。

しかし、手続き的なJavaから、宣言的なJavaへ移行するにも役立つはずです。

トピックはだいたい1ページで収まっており、読みやすいのもいいです。

ただ、Kindleだと画像の解像度が低くて、ちょっと読みにくいのが欠点です。翔泳社さん、どうにかしてくれないですかねぇ。

www.amazon.co.jp


さて、ブリ会議で後半に解説したラムダ式がどのように動作するのかについては、次エントリーで紹介する予定です。