ぱと隊長日誌

ブログ運用もエンジニアとしての生き方も模索中

JJUG ナイト・セミナー 「Inside Lambda」レポート

はじめに

JJUG ナイト・セミナー 「Inside Lambda」のレポートを公開します。
JJUG ナイト・セミナー 「Inside Lambda」 7/22(月)開催 | 日本Javaユーザーグループ

今回のレポートの注意点。
スライド資料に無い説明を中心にまとめているため、レポートだけではわかりづらい個所があるかと思います。ぜひスライド資料を併せてご覧ください。
【参考】としている個所は私が挿入しています(補足や参考資料など)。発表者の意図したものではありませんので、その旨ご了承ください。
今回、『Lambda』と『ラムダ』の表記揺れを『Lambda』に統一しました。スライドと表記の異なる場合がありますが、ご了承ください。

Project Lambdaの基礎(櫻庭祐一 / @

入、じゃないよ。λだよ。
アロンゾ チャーチがLambda式を考案した。
これがLISPに始まり、HaskellやScalaにつながっている。

でもJavaのLambda式は他のLambdaとちょっと、いやかなり?違う。

Project Lambdaは3つから構成される。

  • Lambda式
  • その他の文法の変更(ex.デフォルト実装など)
  • API(ex.Streamのような無限リスト)

Lambda式は無名クラスのシンタックスシュガーに過ぎない。
【参考】
この後に発表された宮川さんは、invokedynamic命令を使ってクラスを実行時に生成するという点から、Lambda式が無名クラスの単純なシンタックスシュガーとは言えないのでは?という意見を出されていました。
この後の宮川さんの発表資料の『総括』をご覧ください。

関数型インタフェースは実装すべきメソッドが1つだけのもの。
Runnable, Callable, Iterableがその代表例。
IterableのforEachはデフォルト実装になっている。
【参考】
デフォルト実装の導入理由についての解説(翻訳)です。
Java8新機能 ラムダ式とデフォルトメソッドの導入理由 - Blog

Lambda式記述
(引数) -> {式}
引数の型は省略可能。実行時に型の推論を行う。
式が1行なら波括弧とreturnを省略可能。
引数が一つなら丸括弧を省略可能。ただし、型は指定できない。

Lambda式でアンダースコア単独の引数名は使えない。なお、普通のメソッドでもアンダースコア単独の引数名には警告が出る。
ローカル変数が一度しか代入されない場合は実質的にfinalとみなされる。また、再代入はできない。
【参考】
この実質的finalは以下の規則からきています。

無名クラスでローカル変数にアクセスするためには、変数に final を付加する必要があります。

Project Lambda ハンズオン資料 2012.07.25

詳細は上記引用元の『実質的な final』をご参照ください。

無名クラスとLambda式のthisは異なる。
無名クラスのthisは自分自身を指す。
Lambda式のthisは外側のクラスのthisを指す。

パフォーマンスはLambda式が無名クラスよりも速い。
クラスローディングがなく、動的にクラスを生成しているからか?
【参考】
宮川さんによると、実験結果から必ずしもLambda式のほうがパフォーマンスに優れているとは言えないそうです。環境によって逆転することがあるとのこと。この後の発表資料にて実験結果の一部が紹介されています。

IntelliJNetBeansには無名クラスをLambdaに変換するボタンや設定がある。
【参考】
IntelliJで無名クラスからLabmda、およびその逆変換を行う実例が紹介されています。
IntelliJ12でJDK8のlambda - しおしおの雑記帳

Lambda と invokedynamic の蜜月(宮川拓 / @

論点整理

インスタンスをインタフェースから直接は作れない。間にクラスがいるはず。これが今回の話の肝になる。

Lambda式の実行(1)

匿名クラスはコンパイル時にクラスを生成する。Lambda式は実行時に生成される。

javapコマンドはクラスファイルを逆アセンブルしてくれる。
【参考】
javap - Java クラスファイル逆アセンブラ

Lambda式の実行がinvokedynamic命令の呼び出しに差し替わっている。

invokedynamicを契機に、Lambdaのクラスとインスタンスが生成されていると推測される。

invokedynamicの復習

メソッド呼び出し後、voidの場合は結果がスタックに積まれない。

invokedynamicを導入したのはメソッド再定義などを実現しやすくするため。
これまではJava言語に無い機構(メソッド再定義など)を実現するために処理系が介在していた。これだと、メソッドキャッシュなどの実行時最適化が効きにくい。
invokedynamicを導入することで処理系を介さずに直接メソッドを呼べるようになった。

処理の呼び出しは呼び出し先を特定する必要がある。
ブートストラップメソッドは以下の2つのオブジェクトを生成し、紐づける。

  • CallSiteオブジェクトは呼び出し元に紐づく
  • MethodHandleオブジェクトは呼び出し先の関数ポインタ

ブートストラップメソッドについて。
引数と戻り値の型はコンパイル時に決まっていないといけない。
戻り値としてCallSiteを返す。

【参考】
invokedynamicについて、宮川さんの以前の発表資料でより丁寧に解説されています。

Lambda式の実行(2)

SAM(Single Abstract Method)とはcompareメソッドやrunメソッドなど、実装しないといけない唯一のメソッド。

匿名クラスとほぼ同じとは、匿名クラスだとstatic classにならないが、Lambda式だとstaticになったりならなかったりする。saticだと外側の参照が減る分、メモリリークを避けやすくなる。

なぜinvokedynamic?

LambdaMetaFactoryは今後のJVMでさらに改善されるかもしれない。

invokedynamicで実行時にクラスを生成すると、jarファイルからクラスをロードする時間がかからない分、早くなるかもしれない。ただ、実行環境によって結果が変わるので、何とも言い切れない。

JDK8の最新版だとシングルトンインスタンスストラテラジが採用されている。

リフレクション経由で処理本体を呼び出す事は実現可能だが、遅いからやらないだろう。
MethodHandleのラップはまだ構想レベル。

Lambdaの直列化

Lambdaの直列化の難しさ。直列化の際にはクラスを利用するが、Lambdaのクラスは実行時に生成される。これに対応するためにSerializedLambdaを導入した。

参考

去年のJavaOneのBrain Goetzの資料を参考にするといいかも。

QA

Q.
シングルトンインスタンスになるコードをコピペするとインスタンスは共有される?
A.
今はされない。でも今後はなるかも。

Q.
forEachを全てLambdaで置き換えるべきか?
A.
処理効率はあまり変わらない。
ただ、Stream APIなら話は別。途中の処理が遅延評価されるなど効率化される。

リンク集

tweetまとめ
2013/07/22(#jjug)JJUG Night Seminar ~ Inside Lambda - Togetter

以下の記事でLambda式をJava 8で導入した背景について、キーパーソンの発言や背景を踏まえつつ、考察されています。こうした背景がありそうだと知った上で今回のセミナーを振り返るのも面白いかと思います。
記者の眼 - Java 8は関数型なのか:ITpro