Slide 1

Slide 1 text

今こそ、ラムダ式を考える ラムダ式はどうやって動くのか Java in the Box 櫻庭 祐一

Slide 2

Slide 2 text

ラムダ式 = (無名)関数 ≠ 言語仕様的には

Slide 3

Slide 3 text

ラムダ式 = (無名)関数 ≠ 言語仕様的には Java では、関数は 1st Class Citizen ではない 関数型は提供されていない ラムダ式 : 型システムを変更することなく 現行の型システムの範囲内で 関数のようなものを表したもの

Slide 4

Slide 4 text

ラムダ式 = (無名)関数 ≠ 言語仕様的には 使う時にはそんなことは気にしない 気にする人 いにしえの Java にとらわれている人 JLS/JVMS 厨

Slide 5

Slide 5 text

ラムダ式 = (無名)関数 ≠ 言語仕様的には I think getting worked up over whether Lambdas as expressed in Java SE 8 are "real" closures or not is a pretty unconstructive activity Project Lambda from the Inside. An Interview with Brian Goetz Victor Grazi, InfoQ 2013

Slide 6

Slide 6 text

JVM の動作から見たラムダ式 ラムダ式 = 関数型インタフェースの実装クラスの 特殊なインスタンシエーション ラムダ式 ≠ 関数型インタフェースを実装した 匿名クラスのインスタンシエーション

Slide 7

Slide 7 text

Function func = new Function<>() { public Integer apply(Integer x) { return x+1; }}; 0: new #7 // class IncAnon$1 3: dup 4: invokespecial #9 // Method IncAnon$1."":()V 7: astore_1 Function func = x -> x+1; 0: invokedynamic #7,0 // InvokeDynamic #0:apply:()Ljava/util/function/Function; 5: astore_1

Slide 8

Slide 8 text

匿名クラス IncAnon.java コンパイル IncAnon.class IncAnon$1.class (匿名クラス) new コンストラクターコール 実行 IncLambda.java ラムダ式 IncLambda.class コンパイル invokedynamic ?? 実行

Slide 9

Slide 9 text

InvokeDynamic Java 7で導入されたメソッドコール用バイトコード InvokeVirtual InvokeStatic InvokeInterface InvokeSpecial インスタンスメソッド クラスメソッド インタフェース定義メソッド その他のメソッド InvokeDynamic コールするメソッドを 実行時に動的に検索

Slide 10

Slide 10 text

InvokeDynamic 導入の背景 2000年代 JVM言語の興隆 JRuby Scala Groovy Jython Rhino (JavaScript) et al. 動的型付け言語をJVMで実装しにくい var x = ...; var y = ...; add(x, y); int add(int x, int y) {...} String add(String x, String y) {...} どちらをコールするかは 実行時にならないと決まらない InvokeDynamic

Slide 11

Slide 11 text

InvokeDynamic の動作 登場人物 bootstrapメソッド CallSiteクラス MethodHandleクラス 探索を行い結果をCallSiteで戻すメソッド MethodHandleを保持するコンテナ 実行するメソッドを示す indy 初回 bootstrap 1.メソッド探索 CallSite MethodHandle 2.生成 ターゲットメソッド 3.実行 2回目以降 1.実行

Slide 12

Slide 12 text

bootstrap メソッド探索 メソッド探索以外の 処理を行っても OK 動的メソッド実行以外の用途で Indy の利用が可能 ラムダ式 定数の初期化 文字列連結 String Template

Slide 13

Slide 13 text

ラムダ式の実行 IncLambda.java IncLambda.class コンパイル ラムダ式のメソッドボディを static メソッド化して追加 ラムダ式実行 bootstrap 初回 関数型インタフェース実装クラスを動的生成 メソッドはコンパイル時に生成した static メソッドをコール 動的生成クラスをインスタンシエーションする CallSite を生成 CallSite からインスタンシエーション実行 2回目以降

Slide 14

Slide 14 text

> more IncLambda.java import java.util.function.Function; public class IncLambda { public static void main(String... args) { Function func = x -> x+1; System.out.println(func.apply(0)); } } > more IncLambda.java import java.util.function.Function; public class IncLambda { public static void main(String... args) { Function func = x -> x+1; System.out.println(func.apply(0)); } } > javac IncLambda.java > > more IncLambda.java import java.util.function.Function; public class IncLambda { public static void main(String... args) { Function func = x -> x+1; System.out.println(func.apply(0)); } } > javac IncLambda.java > javap -private IncLambda Compiled from "IncLambda.java" public class IncLambda { public IncLambda(); public static void main(java.lang.String...); private static java.lang.Integer lambda$main$0(java.lang.Integer); } javap クラスファイル解析ツール

Slide 15

Slide 15 text

> more IncLambda.java import java.util.function.Function; public class IncLambda { public static void main(String... args) { Function func = x -> x+1; System.out.println(func.apply(0)); } } > more IncLambda.java import java.util.function.Function; public class IncLambda { public static void main(String... args) { Function func = x -> x+1; System.out.println(func.apply(0)); } } > javac IncLambda.java > > more IncLambda.java import java.util.function.Function; public class IncLambda { public static void main(String... args) { Function func = x -> x+1; System.out.println(func.apply(0)); } } > javac IncLambda.java > javap -private IncLambda Compiled from "IncLambda.java" public class IncLambda { public IncLambda(); public static void main(java.lang.String...); private static java.lang.Integer lambda$main$0(java.lang.Integer); }

Slide 16

Slide 16 text

> javap -c -private IncLambda Compiled from "IncLambda.java" public class IncLambda { <<লུ>> private static java.lang.Integer lambda$main$0(java.lang.Integer); Code: 0: aload_0 1: invokevirtual #34 // Method java/lang/Integer.intValue:()I 4: iconst_1 5: iadd 6: invokestatic #17 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 9: areturn } -c バイトコード出力 private static Integer lambda$main$0(Integer x) { int xx = x.intValue(); xx = xx + 1; return Integer.valueOf(xx); }

Slide 17

Slide 17 text

> javap -v -private IncLambda Compiled from "IncLambda.java" public class IncLambda { <<লུ>> BootstrapMethods: 0: #54 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory: (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String; Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #49 (Ljava/lang/Object;)Ljava/lang/Object; #50 REF_invokeStatic IncLambda.lambda$main$0: (Ljava/lang/Integer;)Ljava/lang/Integer; #53 (Ljava/lang/Integer;)Ljava/lang/Integer; -v 詳細情報出力 > javap -v -private IncLambda Compiled from "IncLambda.java" public class IncLambda { <<লུ>> BootstrapMethods: 0: #54 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory: (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String; Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #49 (Ljava/lang/Object;)Ljava/lang/Object; #50 REF_invokeStatic IncLambda.lambda$main$0: (Ljava/lang/Integer;)Ljava/lang/Integer; #53 (Ljava/lang/Integer;)Ljava/lang/Integer; ラムダ式用の bootstrap 生成したメソッドを bootstrap の引数にする

Slide 18

Slide 18 text

> java -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=true IncLambda 1 > 動的生成したクラスを ファイル出力するオプション > java -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=true IncLambda 1 > ls IncLambda.class IncLambda.java DUMP_LAMBDA_PROXY_CLASS_FILES > cd DUMP_LAMBDA_PROXY_CLASS_FILES > ls IncLambda$$Lambda.0x00000165e0000a00.class > > java -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=true IncLambda 1 > ls IncLambda.class IncLambda.java DUMP_LAMBDA_PROXY_CLASS_FILES > cd DUMP_LAMBDA_PROXY_CLASS_FILES > ls IncLambda$$Lambda.0x00000165e0000a00.class > javap -c IncLambda$$Lambda.0x00000165e0000a00.class final class IncLambda$$Lambda implements java.util.function.Function { public java.lang.Object apply(java.lang.Object); Code: 0: aload_1 1: checkcast #14 // class java/lang/Integer 4: invokestatic #20 // Method IncLambda.lambda$main$0: (Ljava/lang/Integer;)Ljava/lang/Integer; 7: areturn } コンパイル時に 生成したメソッド

Slide 19

Slide 19 text

なぜラムダ式に InvokeDynamic を使用するのか 最適化が容易 new演算子は必ずオブジェクト生成をする必要がある indyであればオブジェクトキャッシュなどの最適化が可能 実装の変更が可能 bootstrapメソッドを更新するだけ 現在、 クラス生成にバイトコード操作LIBのASMを使用しているが 今後Class-File APIへの変更が予想される

Slide 20

Slide 20 text

まとめ ラムダ式は厳密には関数ではないが、 気にしない ラムダ式は匿名クラスとは異なる InvokeDynamicによる柔軟で効率のよい実行

Slide 21

Slide 21 text

今こそ、ラムダ式を考える Java in the Box 櫻庭 祐一