今週末は、G*Workshopです。
http://jggug.doorkeeper.jp/events/3873

ご興味のあるかたは是非どうぞご参加下さい。
今回も、前回に引き続きNTTSOFTの「新」品川オフィスです。
つまりビルが前々回までと異なります。
ご注意くだだい。
今週末は、G*Workshopです。
http://jggug.doorkeeper.jp/events/3873

ご興味のあるかたは是非どうぞご参加下さい。
今回も、前回に引き続きNTTSOFTの「新」品川オフィスです。
つまりビルが前々回までと異なります。
ご注意くだだい。
JJUG CCC 2013 Springのさくらばさんのセッションで「Java 8の怖い話」として、Lambda式をJVM上で実行するにあたり、
という話を聞きました。Lambdaの実行に、内部的にバイトコードを生成して使っている、という話だったのですが、indy使ってるってのは、良く考えると解せないです。Lambdaになんらかの動的な側面があるんでしょうかね???
ということで、調べてみました。
[:contents]
結論から書くと、OracleのJava8のJDKのJavacは、Lambda式の本体を、SAMタイプを継承する内部クラスのメソッドではなく、コンパイル対象のクラスに直接所属するメソッドとしてコンパイルします。また、Lambda式の使用は、invokedynamic命令にコンパイルされます。そして確かに、コンパイル時には、そのメソッドを持つSAMタイプを継承するクラスの.classファイルは生成されません。
そして、invokedynamic命令の初回実行時に、その命令に付随させたブートストラップメソッドの処理の中で、SAMタイプを継承するクラスを動的にオンメモリにバイト列として生成し、それをクラスローダにロードさせ、そのクラスのインスタンスをnewする処理が、invokedynamicの実際の処理に置き換わります*1。この生成されたクラスのSAMメソッドではおそらく、MethodHandle経由で先の(2013/6/13削除。こちらを見ると、MH経由ではない)Lambda本体に相当するメソッドを呼び出します。
ちなみに、「オンメモリにクラスを動的に生成する」という処理を、Java VMではObjectweb asmライブラリを使って行なっています。JVMの中でasmを使ってるとは知らなかった。
indyをLambda式の評価に使ってる理由の一つは、おそらく、lambda式の初回呼び出し時までSAMタイプを継承したクラスの生成およびロードのタイミングを遅延させるためです。この方式なら、例えLambda式が何100個記述されていようとも、そのLambda式が実際に呼び出されなければクラスは全く生成もロードもされないので、クラスロード処理やクラスファイルを読み込むためのファイルアクセス処理が節約でき、起動や初期化の時間が短縮できるのでしょう。また、内部クラスという別クラスではなく、実際にそのクラスにLambda本体のメソッドがあるので、内部クラスを使った場合に生じていた可視性の問題の回避も簡略化できたはずです。
賢いわー。
(2013/06/12 23:52追記)
桜庭さんより(!)Facebookにてコメント頂き、本件に関し、この資料「Lambda: A peek under the hood」が参考になるとのこと。おお!! ざっと見ですが、利点として、字面が同じlambdaは生成SAMクラスを共有できる、とかもあるようです。Type profile pollutionは意味がわかりませんでしたorz。ありがとうございました>桜庭さん
例えば、Lambda式を含む、
public class Test { static void foo(Runnable r) { r.run(); } public static void main(String[] args) { foo(()->{System.out.println("hello");}); } }
こんなJavaソースをまずはコンパイルして実行してみます。
$ javac Test.java $ java Test hello $ ls -la total 16 drwxr-xr-x 4 uehaj staff 136 6 12 22:22 ./ drwxr-xr-x 41 uehaj staff 1394 6 12 22:20 ../ -rw-r--r-- 1 uehaj staff 1095 6 12 22:22 Test.class -rw-r--r-- 1 uehaj staff 182 6 12 22:21 Test.java
確かに、Test.class以外のクラスファイルは生成されてませんね。javapしてみましょう。
$ javap -p Test Compiled from "Test.java" public class Test { public Test(); static void foo(java.lang.Runnable); public static void main(java.lang.String[]); private static void lambda$0(); // ←★ }
★のところの「lambda$0();」がLamda式のボディに対応するメソッドです。
メソッドの宣言だけではなく、メソッド本体のバイトコードとかコンスタントプール情報も見るために、javapに-c -p -vオプションをつけて実行してみます。以降、行番号は判りやすさのために付与しました。
$ javap -p -c -v Test
1 Classfile /Users/uehaj/work/201305/java8_2/y/Test.class
2 Last modified 2013/06/12; size 1095 bytes
3 MD5 checksum d69a213c626bb412c306b8f3ea88cac4
4 Compiled from "Test.java"
5 public class Test
6 SourceFile: "Test.java"
7 InnerClasses:
8 public static final #56= #55 of #59; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
9 BootstrapMethods:
10 0: #25 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
11 Method arguments:
12 #26 invokeinterface java/lang/Runnable.run:()V
13 #27 invokestatic Test.lambda$0:()V
14 #28 ()V
15 minor version: 0
16 major version: 52
17 flags: ACC_PUBLIC, ACC_SUPER
18 Constant pool:
19 #1 = Methodref #9.#21 // java/lang/Object."<init>":()V
20 #2 = InterfaceMethodref #22.#23 // java/lang/Runnable.run:()V
21 #3 = InvokeDynamic #0:#29 // #0:lambda$:()Ljava/lang/Runnable;
22 #4 = Methodref #8.#30 // Test.foo:(Ljava/lang/Runnable;)V
23 #5 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream;
24 #6 = String #33 // hello
25 #7 = Methodref #34.#35 // java/io/PrintStream.println:(Ljava/lang/String;)V
26 #8 = Class #36 // Test
27 #9 = Class #37 // java/lang/Object
28 #10 = Utf8 <init>
29 #11 = Utf8 ()V
30 #12 = Utf8 Code
31 #13 = Utf8 LineNumberTable
32 #14 = Utf8 foo
33 #15 = Utf8 (Ljava/lang/Runnable;)V
34 #16 = Utf8 main
35 #17 = Utf8 ([Ljava/lang/String;)V
36 #18 = Utf8 lambda$0
37 #19 = Utf8 SourceFile
38 #20 = Utf8 Test.java
39 #21 = NameAndType #10:#11 // "<init>":()V
40 #22 = Class #38 // java/lang/Runnable
41 #23 = NameAndType #39:#11 // run:()V
42 #24 = Utf8 BootstrapMethods
43 #25 = MethodHandle #6:#40 // invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
44 #26 = MethodHandle #9:#2 // invokeinterface java/lang/Runnable.run:()V
45 #27 = MethodHandle #6:#41 // invokestatic Test.lambda$0:()V
46 #28 = MethodType #11 // ()V
47 #29 = NameAndType #42:#43 // lambda$:()Ljava/lang/Runnable;
48 #30 = NameAndType #14:#15 // foo:(Ljava/lang/Runnable;)V
49 #31 = Class #44 // java/lang/System
50 #32 = NameAndType #45:#46 // out:Ljava/io/PrintStream;
51 #33 = Utf8 hello
52 #34 = Class #47 // java/io/PrintStream
53 #35 = NameAndType #48:#49 // println:(Ljava/lang/String;)V
54 #36 = Utf8 Test
55 #37 = Utf8 java/lang/Object
56 #38 = Utf8 java/lang/Runnable
57 #39 = Utf8 run
58 #40 = Methodref #50.#51 // java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
59 #41 = Methodref #8.#52 // Test.lambda$0:()V
60 #42 = Utf8 lambda$
61 #43 = Utf8 ()Ljava/lang/Runnable;
62 #44 = Utf8 java/lang/System
63 #45 = Utf8 out
64 #46 = Utf8 Ljava/io/PrintStream;
65 #47 = Utf8 java/io/PrintStream
66 #48 = Utf8 println
67 #49 = Utf8 (Ljava/lang/String;)V
68 #50 = Class #53 // java/lang/invoke/LambdaMetafactory
69 #51 = NameAndType #54:#58 // metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
70 #52 = NameAndType #18:#11 // lambda$0:()V
71 #53 = Utf8 java/lang/invoke/LambdaMetafactory
72 #54 = Utf8 metaFactory
73 #55 = Class #60 // java/lang/invoke/MethodHandles$Lookup
74 #56 = Utf8 Lookup
75 #57 = Utf8 InnerClasses
76 #58 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
77 #59 = Class #61 // java/lang/invoke/MethodHandles
78 #60 = Utf8 java/lang/invoke/MethodHandles$Lookup
79 #61 = Utf8 java/lang/invoke/MethodHandles
80 {
81 public Test();
82 flags: ACC_PUBLIC
83 Code:
84 stack=1, locals=1, args_size=1
85 0: aload_0
86 1: invokespecial #1 // Method java/lang/Object."<init>":()V
87 4: return
88 LineNumberTable:
89 line 3: 0
90
91 static void foo(java.lang.Runnable);
92 flags: ACC_STATIC
93 Code:
94 stack=1, locals=1, args_size=1
95 0: aload_0
96 1: invokeinterface #2, 1 // InterfaceMethod java/lang/Runnable.run:()V
97 6: return
98 LineNumberTable:
99 line 5: 0
100 line 6: 6
101
102 public static void main(java.lang.String[]);
103 flags: ACC_PUBLIC, ACC_STATIC
104 Code:
105 stack=1, locals=1, args_size=1
106 0: invokedynamic #3, 0 // InvokeDynamic #0:lambda$:()Ljava/lang/Runnable;
107 5: invokestatic #4 // Method foo:(Ljava/lang/Runnable;)V
108 8: return
109 LineNumberTable:
110 line 8: 5
111 line 9: 8
112
113 private static void lambda$0();
114 flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
115 Code:
116 stack=2, locals=0, args_size=0
117 0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
118 3: ldc #6 // String hello
119 5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
120 8: return
121 LineNumberTable:
122 line 8: 0
123 }
長いっ!
なので以降、分解して見ていきます。
Lambdaの本体に対応しそうなのは、
113 private static void lambda$0(); 114 flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC 115 Code: 116 stack=2, locals=0, args_size=0 117 0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 118 3: ldc #6 // String hello 119 5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 120 8: return 121 LineNumberTable: 122 line 8: 0 123 }
の部分であり、確かにlambda$0メソッドが、
()->{ System.out.println("hello"); }
の「System.out.println("hello")」近辺に対応していそうです。
まず、mainメソッドに対応する以下の部分を見ると、
102 public static void main(java.lang.String[]); 103 flags: ACC_PUBLIC, ACC_STATIC 104 Code: 105 stack=1, locals=1, args_size=1 106 0: invokedynamic #3, 0 // InvokeDynamic #0:lambda$:()Ljava/lang/Runnable; 107 5: invokestatic #4 // Method foo:(Ljava/lang/Runnable;)V 108 8: return 109 LineNumberTable: 110 line 8: 5 111 line 9: 8
確かにinvokedynamic命令が生成されていることがわかります。このinvokedynamic命令に対応する「ブートストラップメソッド」は、javap出力の冒頭にある
9 BootstrapMethods: 10 0: #25 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; 11 Method arguments: 12 #26 invokeinterface java/lang/Runnable.run:()V 13 #27 invokestatic Test.lambda$0:()V 14 #28 ()V
ここのところですね。ブートストラップメソッドが表している実体は、静的メソッドへのポインタであり、ここでは具体的には、JDKの提供するAPIであるメソッドjava.lang.invoke.LambdaMetafactory.metaFactory()です。
ブートストラップメソッドとして仕掛けられているLambdaMetaFactoryは、このケースでは最終的にjava.lang.invoke.InnerClassLambdaMetafactory.javaのspinInnerClass()あたりの以下の処理を呼び出します。
(略) import jdk.internal.org.objectweb.asm.*; import static jdk.internal.org.objectweb.asm.Opcodes.*; (略) private final ClassWriter cw; // ASM class writer (略) private Class<?> spinInnerClass() throws LambdaConversionException { (略) cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC, lambdaClassName, null, NAME_MAGIC_ACCESSOR_IMPL, interfaces); // Generate final fields to be filled in by constructor for (int i = 0; i < argTypes.length; i++) { FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, argNames[i], argTypes[i].getDescriptor(), null, null); fv.visitEnd(); } generateConstructor(); MethodAnalyzer ma = new MethodAnalyzer(); // Forward the SAM method if (ma.getSamMethod() == null) { throw new LambdaConversionException(String.format("Functional interface method not found: %s", samMethodType)); } else { generateForwardingMethod(ma.getSamMethod(), false); } // Forward the bridges // @@@ The commented-out code is temporary, pending the VM's ability to bridge all methods on request // @@@ Once the VM can do fail-over, uncomment the !ma.wasDefaultMethodFound() test, and emit the appropriate // @@@ classfile attribute to request custom bridging. See 8002092. if (!ma.getMethodsToBridge().isEmpty() /* && !ma.conflictFoundBetweenDefaultAndBridge() */ ) { for (Method m : ma.getMethodsToBridge()) { generateForwardingMethod(m, true); } } if (isSerializable) { generateWriteReplace(); } cw.visitEnd(); // Define the generated class in this VM. final byte[] classBytes = cw.toByteArray(); ClassLoader loader = targetClass.getClassLoader(); // ★★ (略) return (Class<?>) Unsafe.getUnsafe().defineClass(lambdaClassName, classBytes, 0, classBytes.length, loader, pd); }
asmのClassWriterで生成したバイト列classBytesを、★★以降でクラスローダでロードしているようです。
どういうクラスを生成しているか、までは実は追ってないのですが、MethodHandle経由で渡してきたlambda$0をinvokeするようなコードを含むメソッドを含むクラスが生成されるのではないかと思います(もしくはlambda$0のバイトコード本体をコピーする??まさかね)。(2013/6/13削除)
こちらを見ると、直接lambda$0メソッドをinvokevirtual/invokestaticで呼んでいるようです(2013/6/13追記)。
APIリファレンスが日本語に訳されたので、invokedynamicあたりのAPI(MethodHandle, CallSite,....)を初めて読む気になりました(へたれ)。ありがとうオラクルの人(寺田さん?)。
*1:この置き換えはinvokedynamicの通常動作です。CallSiteのインストールの話ね。
socket.ioがJavaやGrailsから扱えるかを調べている関係でvert.xを調べていて興味深かったので、こちらにあるVert.xの記事を翻訳してみました。JGGUG G*Workshopにおける杉浦さんのVert.x資料もお奨めです。
Vert.xは急速に発達つつあるサーバ・フレームワークです。
世にあまたあるサーバ・フレームワークのいずれもが、多様なプロトコルをサポートし、高速であることが特長であると主張していますが、Vert.xはそれらよりも一歩抜きん出ています。例えば、Vert.xは、サーバサイドのネットワーク環境の確立と操作も対象としています。言いかえれば、Vert.xは、単一サーバ上のデーモン実行だけでなく、クラスタリング環境での複数サーバデーモンの実行を考慮しているのです。
したがって、Vert.xを調査するにあたっては、どのように高性能を実現しているかだけではなく、それが考慮しているネットワーク環境がどのようなものであるかを見ていくことが重要です。Vert.xの構造を調べることには、時間をかけるだけの価値があると思います。
Vert.xはNode.jsから影響を受けているプロジェクトです。Node.jsと同様に、Vert.xはイベントベースのプログラミング・モデルを提供するサーバ・フレームワークです。したがって、Vert.x APIは、Node.jsのそれと非常に似ています。なぜならこれらのモデルは両方とも非同期APIを提供するからです。
Node.jsはJavaScriptで開発されているのに対し、Vert.xはJavaで開発されています。しかし、単に「Node.jsのJavaバージョン」としてVert.xを理解するのは単純すぎる見方です。Vert.xはNode.jsの影響を受けていますが、Node.jsとは確かに異なるユニークな哲学を持っています。
Vert.xの設計哲学のコアは以下のように要約できます:
Vert.xの性能について議論する前に、NettyとVert.xの関係をまとめておくと、それは「Vert.xはNettyを使用する」ということです。言いかえれば、Vert.xのIOはすべてNettyによって処理されます。したがって、Vert.xとNettyのパフォーマンスの差異を検証することに意味はありません。
Vert.xは、Nettyとは異なる独立したAPIおよび機能を提供するサーバ・フレームワークであり、またNettyとは異なる目的で設計されています。Nettyは低レベルIOを処理するフレームワークであり、Vert.xはNettyよりも高いレベルのIOを処理します。
Vert.xによって提供される機能はNode.jsのものとは異なっているとしても、パフォーマンスの比較は重要です。以下の図1および図2は、Vert.x(Java、Ruby、Groovy)とNode.jsの性能を示しています(ソースは: http://vertxproject.wordpress.com/2012/05/09/vert-x-vs-node-js-simple-http-benchmarks/)。
図1は、HTTPサーバを構築し、200/OKレスポンスだけを返す場合のパフォーマンスの比較で、図2は、72バイトの静的なHTMLファイルがレスポンスとして返される場合のパフォーマンスの比較を示しています。
図1: 200/OKレスポンスだけが返された場合のパフォーマンスの比較

図2:72バイトのスタティックなHTMLファイルが返される場合のパフォーマンスの比較

上記のパフォーマンス比較はVert.x開発者によって提示されたものであり、テストは厳密な環境の下では行なわれていません。相対的な差異だけを見てください。
一点、注目すべきポイントとしては、Vert.xでのJavaScriptの性能がNode.jsよりも良いということです。とはいえ、この性能比較が信頼できるとしても、このことだけをもってして「Vert.xがNode.jsより良い」と言うのは難しいでしょう。Node.jsはSocket.ioのような素晴しいモデルを提供しており、参考資料も充実しているからです。
Vert.xは独自の用語を定義したり、いくつかの一般的な用語をVert.x用に再定義しています。したがって、Vert.xを理解するためには、Vert.xで定義された用語を理解することが必要です。下記はVert.xを使う上で頻繁に使う用語です:
図3:Vert.xアーキテクチャー(出所: http://www.javacodegeeks.com/2012/07/osgi-case-study-modular-vertx.html )
Vert.xのデフォルト実行ユニットはVerticleです。また、いくつかのVerticleは、同時に1つのVert.xインスタンス上で実行することができます。Verticleはイベントループ・スレッド上で実行されます。Vert.xインスタンス群はネットワーク上の1つのホストだけでなく、複数のホスト上でも実行することができます。この際、Verticlesやモジュールはイベントバスで通信します。
まとめると、vert.xアプリケーションはVerticle群とモジュールで構成されており、それらの通信はイベント・バスで行います。

全体の構成は以下のとおりです:
Vert.xは、Java 7のinvokeDynamicを使用するため、JDK7が必須です。Vert.xのインストールは容易です。
以上がすべてです(訳注: gvm Toolを使ってもvert.xをインストール可能です)。コマンド・ウィンドウ中で、vertx versionを実行してください。バージョン情報が成功裡に表示されれば、インストールは完了です。
さて、JavaScriptで「Hello World!」を表示する単純なWebサーバーを構築し実行しましょう。
次のコードをserver.jsというファイルに保存してください。
これは、Node.jsのコードとほとんど同一です。
load('vertx.js');
vertx.createHttpServer().requestHandler(function(req) {
req.response.end("Hello World!");}) .listen(8080, 'localhost');vertxコマンドを使用して、作成したserver.jsアプリケーションを実行します:
$ vertx run server.js
ブラウザでhttp://localhost:8080を開き、「Hello World!」が表示されれば成功です。
別の例を、別の言語を使ってやってみましょう。次のコードはJavaで書かれています。静的ファイルの内容を読みとって、HTTPレスポンスとして返すWebサーバーです。
Vertx vertx = Vertx.newVertx(); vertx.createHttpServer().requestHandler(new Handler() { public void handle(HttpServerRequest req) { String file = req.path.equals("/") ? "index.html" : req.path; req.response.sendFile("webroot/" + file); } }).listen(8080);
次のコードは、まったく同じ機能を持ちますがGroovyで書いた場合です:
def vertx = Vertx.newVertx() vertx.createHttpServer().requestHandler { req -> def file = req.uri == "/" ? "index.html" : req.uri req.response.sendFile "webroot/$file" }.listen(8080) Future of Vert.x and NHN
NHN社において我々はVert.xを高く評価しており、公式リリース以前からVert.x開発に注目しています。私たちはVert.xを改善について議論するために2012年6月以来、Vert.xの主な開発者(Tim Fox)と連絡を取っています。例えば、Vert.xの上のSocket.ioについてです。
Socket.ioはNode.jsだけで利用可能でしたが、私たちはJavaに移植し、Gitubの上のVert.xリポジトリにPull Requestを送りhttps://github.com/vert-x/vert.x/pull/320、今まさに、Vert.x-modプロジェクトにマージされるところです。私たちの努力であるsocket.io vert.xモジュールは、NHNで開発しているRTCS 2.0バージョン(vert.x+Socket.io)で使用されます。
Node.jsの人気の理由の一つは、Socket.ioのためであるかもしれず、Vert.xでもSocket.ioを使用できるようになれば、Vert.xの用途は広がるかもしれません。更に、このsocket.io vertxモジュールを埋め込みライブラリとして使用することで、Javaベースのアプリケーション中でsocket.ioを使用できるようになります。
RTCS(瞬時通信システム)はNHNによって作られたリアル・タイム・ウェブ開発プラットフォームであり、ブラウザとサーバ間のメッセージのリアル・タイム転送を支援します。
RTCSはBaseball 9、Me2Dayチャット、BANDチャットのようなNHNウェブサービスのために使われています。
Vert.xの最初のバージョンは2012年5月にリリースされたので、2009年に最初のバージョンがリリースされたNode.jsと比較すると、Vert.xの歴史は非常に短く、参考資料は多くありません。しかし、Vert.xはVMwareに支援されており、CloudFoundryの上で動作することもできます。なので、多くの参考資料がすぐに入手可能になるだろうと期待しています。
著者
Web Platform Development Labのソフトウェア・エンジニア、Seongmin Wooによる。
参照
今日は朝早くから、西新宿でJJUG CCC 2013 Springに参加してきました。(まとめ)
久々の1日の勉強会で楽しかったです。あと都庁を間近かで見れてよかった。
ブログ書くまでが勉強会、という基本を新ためて学んだので、書きます。
変容することが確実なこれからのシステム開発、それに耐え得る「サステイナブルSI」を考える。
途中から入りました(すみません)ので半端なメモです。
JavaFX、ちゃんと生きてます!、という話。
Java EE 7の情報。押えておくべき。
基本をしっかりと。これを聞いてからGroovy資料作れたなら楽ができたのにorz。
Java8で新規導入される「型アノテーション」なる言語機能について。
個人的には今回このセッションがツボでした。使いやすいかどうかはともかく、野心的なところがイイね! 個人的要望としてはJava10あたりでASTを変更可能にして欲しいw。
例:
MyObject m = new @Interneed MyObject(); //new対象のクラス MyObject n = new @NonEmpty @ReadOnly MyObject(); //new対象のクラス class Hoge implements @Readonly List<@Readonly String> {} //ジェネリクスの型引数 MyObject xxx = (@NonNull String) myObject; //キャスト int @ReadOnly[]@ReadOnly[] a = .. int @ReadOnly[][] a = .. int []@ReadOnly[] a = ..
//*>>> @ReadOnly...
/*>>> @ReadOnly... */
あてくしの発表。人数多い。JGGUGとは違うのだよ!状態。
しかしまさかのプロジェクタ出力できず。
急遽Windows PCをお借りして2分ロスしてスタート。
PC貸してくださいました方ありがとうござました。
USB接続のRGBコネクタを持ってくるべきだったな。
でも結果としては無事になんとか。(かな?)
G*ワークショップZ Spockハンズオンのおしらせ。
Spockを試してみたいかたは是非どうぞ。
NTTソフトウェアはオフィスが移転したので、今回から場所がいつもと違います。品川の同じ並びの別のビルです。
という記事を書きましたが、今回はJava 8 Lambda式を使って書きなおしてみました。
import java.util.*; import java.util.function.*; import java.util.stream.*; class BusJava8 { int ceil10(int n) { return (int)(Math.ceil(n/10) * 10); } int half(int n) { return ceil10(n/2); } List parse(String input) { String[] tmp = input.split(":", 0); String p = tmp[0]; String list = tmp[1]; return Arrays.asList(Integer.parseInt(p), Arrays.asList(list.split(",", 0))); } int calc(int basePrice, List<String> passengers) { Map<String,Function<Integer,Integer>> map = new HashMap<>(); map.put("An", (Integer it)->it); map.put("Ap", (Integer it)->0); map.put("Aw", (Integer it)->half(map.get("An").apply(it))); map.put("Cn", (Integer it)->half(map.get("An").apply(it))); map.put("Cp", (Integer it)->0); map.put("Cw", (Integer it)->half(map.get("Cn").apply(it))); map.put("In", (Integer it)->half(map.get("An").apply(it))); map.put("Ip", (Integer it)->0); map.put("Iw", (Integer it)->half(map.get("In").apply(it))); Map<String,List<List>> groups =passengers.stream() .map(it -> Arrays.asList(it, map.get(it).apply(basePrice))) .collect(Collectors.groupingBy(it -> (((String)it.get(0)).substring(0,1)))); long size = (long)(groups.get("I").size()-groups.get("A").size()*2); groups.get("I").sort((a, b) -> (Integer)(a.get(1)) - (Integer)(b.get(1))); List<List> tmp = new ArrayList(groups.get("A")); tmp.addAll(groups.get("C")); tmp.addAll(groups.get("I").stream().limit(size).collect(Collectors.toList())); return tmp.stream().mapToInt(it->(Integer)it.get(1)).sum(); } static void test(String input, String answer) { Java8 b = new Java8(); List tmp = b.parse(input); int basePrice = (int)tmp.get(0); List<String> passengers = (List<String>)tmp.get(1); int expected = Integer.parseInt(answer); int result = b.calc(basePrice, passengers); assert result == expected; } public static void main(String[] args) { test("1480:In,An,In,In,In,Iw,Cp,Cw,In,Aw,In,In,Iw,Cn,Aw,Iw", "5920"); } }
上では、自分がJava8に全く不慣れなため、非効率・冗長な記述をしているところがあるかもしれません。また、そもそも元のクロージャを駆使したアルゴリズムがGroovy向きだった、など比較としてはフェアではないかもしれませんので、ご留意を。Java 8Lambdaの仕様も未確定だと思いますので、現時点でのeaでの仕様であることも注意ください。
で、感想を言うと、こりゃ書くのたいへんだわ、です。Java8の補完対応IDEを使わなかったこと、などもあると思いますが…。あと結構エラーメッセージが怖いです。たとえば
Java8.java:28: エラー: 不適合な型: 推論変数Rには、不適合な境界があります .collect(Collectors.groupingBy(it -> (((String)(it.get(0))).substring(0,1))));
^
等価制約: Map>>
上限: Map,Object
R,Tが型変数の場合:
メソッドcollect(Collector)で宣言されているRはObjectを拡張します
インタフェース Streamで宣言されているTはObjectを拡張します
INT#1,INT#2がintersection型の場合:
INT#1はObject,Serializable,Comparableを拡張します
INT#2はObject,Serializable,Comparableを拡張します
とか。型指定を1つ間違えただけで上のメッセージは…。
他に、Groovyと比べると、例えLambdaを使っても、
などにより、冗長性は高くなるようです。シンプルなケースを除き、高階関数をバリバリ使って関数型ライクに書くのは余り積極的になれない気がしました。リリースまでには多くが解決されてしまうかもしれませんが*1。
Lambda式の良い点は、GroovyのCompileStticに比べても、型推論の精度が高いということ。GroovyのCompileStaticでは、クロージャの戻り値の型は推論に寄与してないようで、チェインすると2個目以降に明示指定が必要になります。
さて、速度はどうだったか、などはJJUG CCC 2013 Springのセッションにて(ステマ)。
*1:まあ、lambdaの主眼はマルチコア細粒度並列処理と言われているので、もしそうなら通常の処理にあえて使う必然性もないわけですけれども。
オフラインどう書く第九回の問題をやってみたという記事を書きましたが、今回はCompileStaticアノテーションを使って書きなおしてみました。
import groovy.transform.* @CompileStatic int ceil10(int n) { (Math.ceil(n/10) * 10) as Integer } @CompileStatic int half(int n) { ceil10(n/2) } @CompileStatic List parse(String input) { String[] tmp = input.split(':') String p = tmp[0] String list = tmp[1] return [Integer.parseInt(p), list.split(",") as List] } @CompileStatic int calc(int basePrice, List<String> passengers) { Map<String,Closure> map map = [An:{int it->it}, Ap:{int it->0}, Aw:{int it->half((int)map.get('An')(it))}, Cn:{int it->half((int)map.get('An')(it))}, Cp:{int it->0}, Cw:{int it->half((int)map.get('Cn')(it))}, In:{int it->half((int)map.get('An')(it))}, Ip:{int it->0}, Iw:{int it->half((int)map.get('In')(it))}, ] Map<String,List> groups = passengers.collect{String it -> [it, map.get(it)(basePrice)]}.groupBy{List<String> it -> it[0][0]}.withDefault{[]} return ((groups.get('A')+groups.get('C')+groups.get('I').sort{ List<Integer> it -> it[1] }.take(groups.get('I').size()-(groups.get('A').size()*2))).sum{List<Integer> it -> it[1]}) as Integer } @CompileStatic void test(String input, String expected) { List tmp = parse(input) int basePrice = (int)tmp[0] List<String> passengers = (List<String>)tmp[1] assert Integer.parseInt(expected)==calc(basePrice, passengers) } test( "1480:In,An,In,In,In,Iw,Cp,Cw,In,Aw,In,In,Iw,Cn,Aw,Iw", "5920" );
マップの要素指定がプロパティ形式でアクセスできなくなったり、クロージャの暗黙引数が省略できないケース(型推論できない場合)があったり、結構冗長になっていることがわかります。ちなみに上記の書き直しのために、staticalizerは役立ちます。
あと正しいTupleが欲しい。
本記事は、JJUG CCC 2013 Springのステマです。