※当サイトの記事には、広告・プロモーションが含まれます。

Javaの「ラムダ式(Lambda Expression)」は何を実現したかったのか

f:id:ts0818:20211011180515j:plain

gigazine.net

スポーツや作業に集中すると、他の物事への意識が薄れ、時間が遅く進むように感じるゾーン(フロー)に入ることがあります。また、バスケットボールなどのチームスポーツや、複数人での楽器演奏、マルチプレイゲームなどが「チームフロー」と呼ばれる複数人で同時にゾーンに入る状態を引き起こすことも知られています。このチームフローについて東北大学豊橋技術科学大学を含む国際的な研究チームが分析を行い、チームフローに特有な脳の状態を明らかにしました。

スポーツやゲームで複数人で「ゾーン」に入ると脳が同期されることが判明 - GIGAZINE

⇧ 以心伝心みたいな感じで、エスパーの世界が訪れる未来が来るんだろうか...

いつもの如く、冒頭から脱線しましたが、というわけで、Javaの「ラムダ式(Lambda Expression)」などについて調べてみました。

レッツトライ~。

 

Javaの「ラムダ式(Lambda Expression)」とは?

Wikipediaさんに聞いてみると、あくまで、一般的な「ラムダ式(Lambda Expression)」の説明では、

Lambda expression may refer to:

  • Lambda expression in computer programming, also called an anonymous function, is a defined function not bound to an identifier.
  • Lambda expression in lambda calculus, a formal system in mathematical logic and computer science for expressing computation by way of variable binding and substitution.

https://en.wikipedia.org/wiki/Lambda_expression

⇧「anonymous function」ってことで、日本語だと「匿名関数」ってことになるのかね。「匿名関数」はと言うと、

In computer programming, an anonymous function (function literallambda abstractionlambda functionlambda expression or block) is a function definition that is not bound to an identifier. Anonymous functions are often arguments being passed to higher-order functions or used for constructing the result of a higher-order function that needs to return a function.

https://en.wikipedia.org/wiki/Anonymous_function

⇧「関数」の「引数」に利用できる「関数」、それが「ラムダ式(Lambda Expression)」ってことらしい、それ以外にも「関数」の実装で利用できる「関数」でもあるらしい。

そもそもとして、一般的な「ラムダ式(Lambda Expression)」は「ラムダ計算」が関係してるらしく、

ラムダ計算(ラムダけいさん、英語lambda calculus)は、計算模型のひとつで、計算の実行を関数への引数評価英語evaluation)と適用英語application)としてモデル化・抽象化した計算体系である。ラムダ算法とも言う。関数を表現する式に文字ラムダ (λ) を使うという慣習からその名がある。

ラムダ計算 - Wikipedia

ラムダ計算は1つの変換規則(変数置換)と1つの関数定義規則のみを持つ、最小の(ユニバーサルな)プログラミング言語であるということもできる。ここでいう「ユニバーサルな」とは、全ての計算可能な関数が表現でき正しく評価されるという意味である。これは、ラムダ計算がチューリングマシンと等価な数理モデルであることを意味している。チューリングマシンがハードウェア的なモデル化であるのに対し、ラムダ計算はよりソフトウェア的なアプローチをとっている。

ラムダ計算 - Wikipedia

⇧ という説明になっており、

非形式的な概説

例えば、ある数に 2 を加える関数 f を考える。これは通常の書き方では f(x) = x + 2 と書くことができるだろう。この関数 f は、ラムダ計算の式(ラムダ式という)では λxx + 2 と書かれる。変数 x の名前は重要ではなく、 λyy + 2 と書いても同じである。

ラムダ計算 - Wikipedia

⇧ ってあることことから、「ラムダ計算」で「関数を表現した式」のことを「ラムダ式(Lambda Expression)」と言うそうな。

同様に、この関数に 3 を適用した結果の数 f(3) は (λxx + 2) 3 と書かれる。関数の適用は左結合である。つまり、 f x y = (f xy である。今度は、引数(関数の入力)に関数をとりそれに 3 を適用する関数を考えてみよう。これはラムダ式では λff 3 となる。この関数に、先ほど作った 2 を加える関数を適用すると、 (λff 3) (λxx + 2) となる。ここで、

ff 3) (λxx + 2)    と    (λxx + 2) 3    と    3 + 2

の3つの表現はいずれも同値である。

ラムダ計算 - Wikipedia

ラムダ計算では、関数の引数は常に1つである。引数を2つとる関数は、1つの引数をとり、1つの引数をとる関数を返す関数として表現される(カリー化)。例えば、関数 f(xy) = x − y は λx. (λyx − y) となる。この式は慣例で λxyx − yと省略して書かれることが多い。以下の3つの式

xyx − y) 7 2    と    (λy. 7 − y) 2    と    7 − 2

は全て同値となる。

ラムダ計算 - Wikipedia

ラムダ計算そのものには上で用いた整数や加算などは存在しないが、算術演算や整数は特定のラムダ式の省略であると定義することによってエンコードできる。

ラムダ計算 - Wikipedia

⇧ まぁ、「ラムダ式(Lambda Expression)」を実現するには、「関数」という仕組みが必要らしい。 

そして、まさかの、

型付きラムダ計算typed lambda calculus)とは、無名の関数の抽象表現にラムダ () というシンボルを用いる型付き形式手法である。

型付きラムダ計算 - Wikipedia

ある観点から見れば、型付きラムダ計算は型を持たないラムダ計算を改良したものと言えるが、別の観点からは、より根本的な理論と見ることもでき、型を持たないラムダ計算の方が型が1つしかない特殊ケースと見ることができる。

型付きラムダ計算 - Wikipedia

⇧「型付きラムダ計算」とかあるんだけど...

「ラムダ計算」は、

  • 型付きラムダ計算
    • 型無しラムダ計算

みたいな関係ってことなんかね?

ただ、Javaにおける「ラムダ式(Lambda Expression)」はちょっと事情が異なってくるらしい。

 

Javaの「ラムダ式(Lambda Expression)」が生まれた経緯は

Oracleさんが、ブログでJavaの「ラムダ式(Lambda Expression)」について触れてました。

Javaコードの内部、そしてJVMの内部で、ラムダ式はどのように見えるのでしょうか。何らかの種類の値であることは間違いありませんが、Javaではプリミティブタイプとオブジェクト参照という2種類の値しか許可されていません。ラムダ式がプリミティブタイプではないのは明らかであるため、オブジェクト参照を返す何らかの式であるに違いありません。

https://blogs.oracle.com/otnjp/post/behind-the-scenes-how-do-lambda-expressions-really-work-in-java-ja

⇧ 上記サイト様で、Javaコンパイルされた「バイトコード」で考察してるようです。

何故にJavaに「ラムダ式」が導入されたかという背景なんかは、

builder.japan.zdnet.com

 「JSR 292: Supporting Dynamically Typed Languages on the Java Platform」は、もともとはOpenJDKの「Da Vinci Machine Project」と呼ばれるプロジェクトとして発足したものだ。同プロジェクトの目的は、JVM上でJava以外の言語のランタイムをより簡単に実装できるようにすることにある。その背景には、JRubyやGroovy、Jythonなど、JVM上で動作する軽量言語が多数登場してきたことがあった。

Java SE 8のラムダ式はどう実現されたのか?──実装の経緯、内部的な仕組みを理解する - builder by ZDNet Japan

 バック氏は、「JVM上にJava以外の言語のランタイムを実装する際、特にネックとなったのが、動的な型付けのメソッド呼び出しをサポートしていないことでした」と振り返る。従来のJVMには、次の4種類のメソッド呼び出しの命令が用意されていた。

  • invokevirtual: インスタンス・メソッド
  • invokeinterface: インタフェースのメソッド
  • invikestatic: 静的メソッド
  • invokespecial: その他のメソッド(コンストラクタ、スーパークラス、privateメソッドなど)

 「これら4種類の命令は、Java言語に限って言えば、極めて適切なものです。しかし、他の言語にとっては必ずしも十分ではない場合がありました。JSR 292では、その"十分ではない場合"を改善することを目指したのです」(バック氏)

Java SE 8のラムダ式はどう実現されたのか?──実装の経緯、内部的な仕組みを理解する - builder by ZDNet Japan

 これらの命令は、ターゲットのメソッドを特定して呼び出すことが前提となっている。Java言語は静的型付け言語であり、コンパイルの段階でターゲットを明示して呼び出しを行うバイトコードを生成する。したがって、上記4種類の命令ですべての要件を満たすことができる。

Java SE 8のラムダ式はどう実現されたのか?──実装の経緯、内部的な仕組みを理解する - builder by ZDNet Japan

 一方、動的型付け言語では、ターゲットの特定をプログラム実行時まで遅らせる必要が生じる。しかし、既存のJVMにはそのような命令が存在しないため、各ランタイム側でメソッド・ディスパッチをエミュレートして動的メソッド呼び出しを仮想的に実現する必要があった。だが、この方法はエミュレーション処理のオーバーヘッドが高いことに加えて、JITコンパイラによる最適化が働かず、プログラムのパフォーマンスが悪いという問題を抱えていた。

Java SE 8のラムダ式はどう実現されたのか?──実装の経緯、内部的な仕組みを理解する - builder by ZDNet Japan

⇧ 上記サイト様にあるように、「動的型付け」で実現してることを「JVMJava Virtual Machine)」で実現したいってことを言ってるぽいんで、Wikipediaさんで「動的型付け」の説明を見てみると、

動的型付け(どうてきかたづけ、dynamic typing)とは、プログラミング言語で書かれたプログラムにおいて、変数や、サブルーチン引数や返り値などの値について、そのを、コンパイル時などそのプログラムの実行よりも前にあらかじめ決めるということをせず、実行時の実際の値による、という型システムの性質のことである。

動的型付け - Wikipedia 

⇧ ってな感じで、「型」を「実行時」に決めるということですね。このあたり「JSR 292: Supporting Dynamically Typed Languages on the Java Platform」っていう「マニュフェスト」とも見解が一致してそうな。

話を戻すと、

 バック氏によれば、JSR 292では当初、Java言語以外のメソッド呼び出しのロジックも直接サポートするようにJVMを拡張する予定であった。しかし、ディスパッチのロジックが言語ごとに異なるため、単にサポートするロジックを増やすだけのアプローチでは対応できなかったという。そこで採用された解決策が、「JVM側ではディスパッチのロジックを固定せず、代わりにディスパッチのロジックを定義するAPIを提供する」というものであった。

Java SE 8のラムダ式はどう実現されたのか?──実装の経緯、内部的な仕組みを理解する - builder by ZDNet Japan

 この方針に基づいてJSR 292で追加されたのが、次の2つの機能である。

 invokedynamicバイトコード命令は、通称「indy(インディ)」と呼ばれる。このindy命令の追加は、「JVMの歴史上、非常に衝撃的な出来事」(バック氏)であった。

Java SE 8のラムダ式はどう実現されたのか?──実装の経緯、内部的な仕組みを理解する - builder by ZDNet Japan

⇧ ってな感じで、「動的型付け」っぽいことを実現するために機能が追加されていて、「java.lang.invoke API」ってものが、

 java.lang.invoke APIindy命令を使ったメソッド・ディスパッチの仕組みを理解するうえでは、invoke APIの次の3つの要素が重要となる。

  • クラスMethodHandle
  • クラスCallSite
  • bootstrapメソッド

Java SE 8のラムダ式はどう実現されたのか?──実装の経緯、内部的な仕組みを理解する - builder by ZDNet Japan

⇧ って3つの要素で構成されてますと。

 

java.lange.reflect」パッケージの機能で「リフレクション」実装すれば「動的型付け」っぽいことできてなかったっけ?

ちょっと気になったのが、「java.lang.reflect」パッケージの機能で「リフレクション」を実装すれば、「動的型付け」っぽいことできてなかったっけ?

このあたり、

メソッド・ハンドルとリフレクションの重要な違いの1つとして、ルックアップ・コンテキストを使った場合は、ルックアップ・オブジェクトが作成されたスコープからアクセスできたメソッドのみが返されるという点が挙げられます。

つまり、メソッド・ハンドルは、どのような環境でも安全に使用できます。

https://www.oracle.com/webfolder/technetwork/jp/javamagazine/Java-ND17-MethodInvoc2.pdf

⇧ ってな感じで、「MethodHandle」と「リフレクション」が比べられてるところからも、何となく「java.lang.reflect」パッケージの機能で実装してた「リフレクション」って「動的型付け」を実現してると言えるのかどうか、ハッキリさせたい気持ちになるよね...

ちなみに「リフレクション」は、

情報工学においてリフレクション (reflection) とは、プログラムの実行過程でプログラム自身の構造を読み取ったり書き換えたりする技術のことを指す。

リフレクション (情報工学) - Wikipedia

⇧ ってな感じで、まさかの「リフレクション」自体が、

  • 動的(実行時)リフレクション
  • 静的(コンパイル時)リフレクション

のように、「動的(実行時)」か「静的(コンパイル時)」で分かれるらしいというね...

かの有名な「stackoverflow」で上がってた疑問によりますと、

stackoverflow.com

What's the difference between java.lang.reflect and java.lang.invoke ?

I know both of us can do reflection but I don't know the difference

In my opinion, reflect allow to collect all the method, field etc and invoke can invoke a method without an object

https://stackoverflow.com/questions/34709845/whats-the-difference-between-java-lang-reflect-and-java-lang-invoke

⇧ ってな「問いかけ」に対して、

I recommend the documentation. To quote the first sentence:

The java.lang.invoke package contains dynamic language support provided directly by the Java core class libraries and virtual machine.

java.lang.reflect, on the other hand, is introspection / reflection.

https://stackoverflow.com/questions/34709845/whats-the-difference-between-java-lang-reflect-and-java-lang-invoke

⇧ ってな「回答」がありましたと。

「introspection」って、また知らん言葉が...

stackoverflow.com

Reflection (taken from oracle java tutorial)

Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.

Introspection (taken from archive of sun java beans tutorial)

Introspection is the automatic process of analyzing a bean's design patterns to reveal the bean's properties, events, and methods. This process controls the publishing and discovery of bean operations and properties.

Introspection uses reflection, the relationship between Introspection and Reflection can be seen as similar to JavaBeans and other Java classes.

It might be worth while to look at "Reflection & Introspection: Objects Exposed" where it goes into detail regarding perfomance and usage. Please note that the article is outdated, 1998.

Hope this helps.

https://stackoverflow.com/questions/2044446/java-introspection-and-reflection

⇧ う~ん...ザックリと「Introspection」は「Reflection」を使うと言っていて、「Reflection」は『Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine.』って言っていて、「動的(実行時)リフレクション」という分類になりそうなように読み取れる。

つまり、「java.lang.reflect」パッケージの機能で実装する「リフレクション」で「動的型付け」っぽいことを実現できるってことかね?

 

java.lang.invoke」パッケージはどうして必要だった?

Oracleの中の人が言うには、

たとえば、リフレクションはコレクションやジェネリクスが導入される前から存在しています。そのため、Reflection APIでは、メソッドのシグネチャがClass[]で表現されています。この表現は、煩雑でエラーが発生しやすくなる可能性があり、冗長性の高いJavaの配列構文によって悪影響を受けることにもなります。また、プリ
ミティブ型を手動でボクシング/アンボクシングすることや、voidメソッドの可能性に対処することが必要なため、さらに複雑になります。

https://www.oracle.com/webfolder/technetwork/jp/javamagazine/Java-ND17-MethodInvoc2.pdf

Java 7では、プログラマーがこういった問題を解決しなくてもいいように、必要な抽象化を行うMethodHandlesと呼ばれる新しいAPIが導入されました。このAPIの中核となっているのがjava.lang.invokeパッケージです。中でも重要なのがMethodHandleクラスです。この型のインスタンスは、メソッドを呼び出す機能を提供します。このインスタンスは、直接実行することもでき、パラメータと戻り型に基づいて動的に型付けされるため、動的に使われる状況でも最大限の型の安全性が提供されます。このAPIはinvokedynamicに必要ですが、単独で使うこともできます。その場合、新しく安全な、リフレクションの代替策と考えることもできます。

https://www.oracle.com/webfolder/technetwork/jp/javamagazine/Java-ND17-MethodInvoc2.pdf

⇧「java.lang.reflect」パッケージで実現する「リフレクション」ってめっちゃ面倒くさいよね、ってことで代替案として「java.lang.invoke」パッケージが導入されたっぽいですね。

 

ラムダ式(Lambda Expression)」と「動的型付け」ってどう関係が?

だいぶ、話が脱線して申し訳ないのですが、

invokedynamicのJVM仕様への導入は、動的言語Javaエコシステムに新たな価値をもたらすものであり、JVM動的言語のサポートを充実させる必要があるという認識の表れです。この新しいバイトコード命令invokedynamicを使用することで、呼出しのターゲットの解決を実行時のロジックまで遅らせることができます。また、コール・サイト・ターゲットの動的な変更にも対応します。さらに、JITコンパイラによる最適化の効果を高めるようなJVMの内部表現もサポートされます。

https://www.oracle.com/webfolder/technetwork/jp/javamagazine/JavaMag-JF13-architect-ponge.pdf

⇧「invokedynamic」っていう「バイトコード命令」、通称「indy」によって「動的型付け」を実現しているということみたいですが、その仕組みはというと、

⇧ 上図のような感じになるらしい。

で、何故か、唐突に「ラムダ式(Lambda Expression)」の話が出てくるんだけど、

 以上のような仕組みを使い、Javaランタイム上でラムダ式を実現するためには、まず型をどのように表現するのかを決めなくてはならない。これについては、いわゆる関数型のような新しい種類の型を定義するというアイデアもあったが、次に挙げる課題を解消するのが難しいため採用には至らなかった。

  • バイトコード側でどのようにして関数型を表現するか
  • 新規の関数インスタンスをどうやって生成するか
  • variance(変位)をどうするべきか

 varianceとは、「instanceofの振る舞いをどう定義するか」ということだ。つまり、例えば「(String -> Boolean)」が「(Object -> Boolean)」の派生型(subtype)だと言えるかどうかという問題である。このルールを定義するとなると仕様が複雑化してしまう。

https://www.oracle.com/webfolder/technetwork/jp/javamagazine/JavaMag-JF13-architect-ponge.pdf

⇧ という感じで、「動的型付け」が実現できる見通しは立ったけど、そもそもとして「ラムダ式(Lambda Expression)」の「型」はどんなものにするの?って議論になった模様。

そして、議論は続き、

 結局、関数型を追加するのは非常に難しいため、代わりに採用されたアイデアが関数インタフェースを使うというものだ。これは1つのAbstractメソッドだけを持つインタフェースのことで、別名「Single Abstract Method(SAM)インタフェース」と呼ばれる。

https://www.oracle.com/webfolder/technetwork/jp/javamagazine/JavaMag-JF13-architect-ponge.pdf

⇧ という形で、Javaで「ラムダ式(Lambda Expression)」を実現するために「関数型インターフェイス」が生み出されたらしい。

つまり、世の中の一般的な「ラムダ式(Lambda Expression)」というのは「関数」が必要だったけど、Javaで「関数」を用意するのは難しいので「関数型インターフェイス」を使いましょう、ということになったわけですね、これがJavaの「ラムダ式(Lambda Expression)」がちょっと様子が異なるという理由ですかね。

で、更に議論は続き、

 このほかにも、さまざまな方式が検討されたものの、どれも決め手に欠けていた。そこで、ラムダ式の仕様を検討していたチームは、問題の本質に立ち返って考えることにしたのだという。その本質とは、「ラムダ式をどのようにしてバイトコードで表現し、実行するか?」ということだ。この問いは、実は次の2つの問いに分けることができる。

 つまり、問題は2つあり、この2つ対して個別に実現方法を検討してもよいわけである。ただし、バイトコードについては、一度表現方法を決めたら、将来にわたって二度と変更することはできない。これは後方互換性の確保に対するJavaの重要な理念であり、曲げることのできない原則である。それに対して、実行の方法についてはランタイム側に任せてしまえばよいため、現段階で固定する必要はない。

Java SE 8のラムダ式はどう実現されたのか?──実装の経緯、内部的な仕組みを理解する - page2 - builder by ZDNet Japan

 そこで、Java SE 8をリリースする段階での解決策として、「バイトコードでは実行方法を固定せず、ランタイムに任せる」というアプローチが採用された。これにより、ランタイム側で実装方法を自由に変えられるようになり、リリース時点の状況に応じて最適な選択肢と取れるようになったわけだ。

Java SE 8のラムダ式はどう実現されたのか?──実装の経緯、内部的な仕組みを理解する - page2 - builder by ZDNet Japan

⇧ ってな感じで、「後方互換性の確保」という点において、「ランタイム(実行時ってことかと)」で対応って方針になったことで、「indy」によって実現した「動的型付け」の機能を使って「ラムダ式(Lambda Expression)」を扱うしかなかったっぽい。

なので、話の流れ的には、

java.lang.reflectどうにかしたいよね」→「java.lang.invoke導入しますか」→「ラムダ式で使えますね」ってな感じになるんですかね?

 

Javaの「ラムダ式(Lambda Expression)」は何を実現したかったのか

まぁ、何て言うか、「動的型付け」を実現する仕組みが必要とされた理由は分かったんだけど、「ラムダ式(Lambda Expression)」が何で必要とされたのかが語られていないんですよね。

そこで、「Project Lambda」っていう「プロジェクト」のドキュメントを見ると、何故に「ラムダ式(Lambda Expression)」が作られたのかが記載されてました。

openjdk.java.net

Project Lambda

JSR 335 (Lambda Expressions for the Java Programming Language) supports programming in a multicore environment by adding closures and related features to the Java language. The JSR has reached its Final Release; these changes to the platform are part of the umbrella JSR 337 and have been integrated into Java SE 8 (modifying the languageJVM, and library specifications).

Project Lambda produced the OpenJDK implementation of these features, now integrated into the jdk8 repository. Binary distributions are available from Oracle; see the JDK 8 Project for more information.

http://openjdk.java.net/projects/lambda/ 

⇧ というか、Javaにおける「ラムダ式(Lambda Expression)」が一体どういうものなのかって説明は一切しないってのは何でなんでしょうね?

本当、IT系のドキュメントって「用語」の説明を端折るよね~。

結局Javaにおける「ラムダ式(Lambda Expression)」が何なのかの説明は見つけることができなかったのだけど、Javaで「ラムダ式(Lambda Expression)」が必要となった背景が記載されたドキュメントが存在していて、

cr.openjdk.java.net

1.Background

Java is, primarily, an object-oriented programming language. In both object-oriented and functional languages, basic values can dynamically encapsulate program behavior: object-oriented languages have objects with methods, and functional languages have functions. This similarity may not be obvious, however, because Java objects tend to be relatively heavyweight: instantiations of separately-declared classes wrapping a handful of fields and many methods.

http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html

⇧「背景」の説明を見ると、「関数型プログラミング」的なプログラミング言語だと「関数」があるんだけど、「オブジェクト型思考プログラミング」的なJavaだと「クラス」を「インスタンス化」して「オブジェクト」を作るけど、Javaの「オブジェクト」って処理を重くするよね、的なニュアンスを醸し出していますと。

説明の続きを読んでいくと、Javaには「匿名クラス」っていうのが存在していて、Javaの多くの有用なライブラリで利用されてるらしいんだけど、「匿名クラス」ってのはコーディング量が増えがちっていう問題、「垂直問題(vertical problem)」があったらしい。(勿論、それ以外にも様々な問題があるらしいけども...)

The biggest pain point for anonymous classes is bulkiness. They have what we might call a "vertical problem": the ActionListener instance from section 1 uses five lines of source code to encapsulate a single aspect of behavior.

http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html

Lambda expressions are anonymous methods, aimed at addressing the "vertical problem" by replacing the machinery of anonymous inner classes with a lighter-weight mechanism.

http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html

⇧ そんな貴方に、はいこちら!

という感じで、「ラムダ式(Lambda Expression)」を導入したら、「垂直問題(vertical problem)」も解決!ってことらしい。

『よもやよもやだ』、まさかの「コーディング量の削減」が主目的だったとは...まぁ、他にも目的はあるでしょうけど。

というわけで、「性能」を上げるために「コーディング量を削減」することでJavaの「オブジェクト」の「軽量化」を目指したJavaの「ラムダ式(Lambda Expression)」ですが、Javaの「関数型インターフェイス」で実現しているらしいので、次回は「関数型インターフェイス」を調べて、実際に使っていきたいですね。

ちなみに、Javaの「ラムダ式(Lambda Expression)」や「関数型インターフェイス」は「Java SE 8」で導入されてるので、旧いJavaだと使えない点は要注意ですかね。

今回はこのへんで。