社内のKotlin勉強会で、Kotlinをプロダクションに導入できるか検討するために調べて話したので、そのスライドの内容を当たり障りのない文章に起こしました。私はモバイルアプリの開発者なので、モバイルアプリ開発者の視点に寄っていると思います。
Runs on JVMとは
Kotlin is a statically-typed programming language that runs on the Java Virtual Machine and also can be compiled to JavaScript source code.
Kotlin (programming language) - Wikipedia, the free encyclopedia
まずはじめに "Runs on JVM" とはどういうことかを考えました。JVMは決められたフォーマットのバイトコードを実行します。たとえば "Hello World" と出力するクラスファイルは以下のようになります。
00000000 ca fe ba be 00 00 00 34 00 1d 0a 00 06 00 0f 09 |.......4........|
00000010 00 10 00 11 08 00 12 0a 00 13 00 14 07 00 15 07 |................|
00000020 00 16 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 |.....<init>...()|
00000030 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e |V...Code...LineN|
00000040 75 6d 62 65 72 54 61 62 6c 65 01 00 04 6d 61 69 |umberTable...mai|
00000050 6e 01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 |n...([Ljava/lang|
00000060 2f 53 74 72 69 6e 67 3b 29 56 01 00 0a 53 6f 75 |/String;)V...Sou|
00000070 72 63 65 46 69 6c 65 01 00 0a 48 65 6c 6c 6f 2e |rceFile...Hello.|
00000080 6a 61 76 61 0c 00 07 00 08 07 00 17 0c 00 18 00 |java............|
00000090 19 01 00 0b 48 65 6c 6c 6f 20 57 6f 72 6c 64 07 |....Hello World.|
000000a0 00 1a 0c 00 1b 00 1c 01 00 05 48 65 6c 6c 6f 01 |..........Hello.|
000000b0 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 |..java/lang/Obje|
000000c0 63 74 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 |ct...java/lang/S|
000000d0 79 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a |ystem...out...Lj|
000000e0 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 |ava/io/PrintStre|
000000f0 61 6d 3b 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72 |am;...java/io/Pr|
00000100 69 6e 74 53 74 72 65 61 6d 01 00 07 70 72 69 6e |intStream...prin|
00000110 74 6c 6e 01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e |tln...(Ljava/lan|
00000120 67 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 |g/String;)V.!...|
00000130 06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 |................|
00000140 09 00 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 |.............*..|
00000150 01 b1 00 00 00 01 00 0a 00 00 00 06 00 01 00 00 |................|
00000160 00 01 00 09 00 0b 00 0c 00 01 00 09 00 00 00 25 |...............%|
00000170 00 02 00 01 00 00 00 09 b2 00 02 12 03 b6 00 04 |................|
00000180 b1 00 00 00 01 00 0a 00 00 00 0a 00 02 00 00 00 |................|
00000190 03 00 08 00 04 00 01 00 0d 00 00 00 02 00 0e |...............|
0000019f
つまり "Runs on JVM" と謳っているプロダクトはJVMのclass verifierをパスするバイトコードを吐くということに等しいということです。なので、アセンブラも "Runs on JVM" ですし
.class public Main
.super java/lang/Object
.method public static main([Ljava/lang/String;)V
.limit stack 2
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Hello World"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
return
.end method
もちろんJavaも "Runs on JVM" ですし
public class Main {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
Kotlinも "Runs on JVM" であり
fun main(args: Array<String>) {
println("Hello World")
}
JVM上で走る何か違いは、その抽象度とパラダイムだと思います。
これだけ見ると最終的にvalidなバイトコードを吐けば何でもいいのではとなってしまうので、もう少し詳しく見てみます。
Kotlinリポジトリについて
KotlinはGitHub上で公開されており(JetBrains/kotlin)、今すぐダウンロードしてコンパイルすることができます。KotlinのissueはGitHubではなく、JetBrainsのBug & Issue TrackerであるYouTrackが使われています(Kotlin (KT) | YouTrack)。
ビルドツールにはantとmavenが使われていますが、とりあえずantがあればいいと思います。
Kotlinのリポジトリを開くとたくさんディレクトリがありますが、とりあえずよく見るのは
- libraries/stdlib
- libraries/tools/kotlin-annotation-processing
- libraries/tools/kotlin-gradle-plugin
- compiler
あたりになるかなと思います。
stdlibとは
Kotlinはランタイムが小さいとよく言われますが、具体的にどういうことなのか手を動かして確認してみましょう。まず "Hello World" を出力する Hello.kt
を書きます。
// Hello.kt
fun main(args: Array<String>) {
println("Hello World")
}
これを javac Hello.java
でクラスファイルを生成して java Hello
で実行するようにkotlincコマンドを使います。生成されたクラスファイルが HelloKt.class
なのは、Kotlinではクラスを定義することなく直接メソッドの定義ができるのですが、JVMの仕様上クラスは必要なのでkotlincがコンパイル時に外側のクラスを生成しているためです。
$ kotlinc Hello.kt
$ kotlin HelloKt
Hello World
"Hello World" が出力されましたね。では好奇心からjavaコマンドでKotlinのクラスファイルを実行してみます。
$ java HelloKt
Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
at HelloKt.main(Hello.kt)
Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics
at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 1 more
NoClassDefFoundErrorになりました。何が起こったのか知るために逆アセンブルしてみます。
$ javap -c HelloKt.class
Compiled from "Hello.kt"
public final class HelloKt {
public static final void main(java.lang.String[]);
Code:
0: aload_0
1: ldc #9 // String args
3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
6: ldc #17 // String Hello World
8: invokestatic #23 // Method kotlin/io/ConsoleKt.println:(Ljava/lang/Object;)V
11: return
}
続いてJavaで書かれたコードのクラスファイルを逆アセンブルした結果です。デフォルトコンストラクタは自動生成されたものなので無視して大丈夫です。
$ javap -c Hello.class
Compiled from "Hello.java"
public class Hello {
public Hello();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello World
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
両者を比べると、Kotlinのクラスファイルには 3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
が追加されています。このクラスがstdlibの中に定義されていて、kotlincはそれがある前提でクラスファイルを吐きます。そのためstdlibがなければ実行時にエラーになります。
コンパイル時に -include-runtime
オプションを渡すことでstdlibが入ったjarを生成することができ、それをjavaコマンドで実行することができるようになります。
$ kotlinc Hello.kt
$ kotlin HelloKt
Hello World
$ kotlinc Hello.kt -include-runtime -d Hello.jar
$ java -jar Hello.jar
Hello World
jarを解凍して中身を見てみると、たしかに自分が書いた HelloKt.class
とstdlibが入っていることが分かります。
Scalaの標準ライブラリと単純にファイル数を比較すると、Kotlinが70ファイルでScalaが751ファイルで、Kotlinのランタイムが小さいということが分かります。
これは、Kotlinは便利なシンタックスと拡張関数(stdlibのほとんどは拡張関数)を提供していて、いわばBetter Javaというポジションなのに対して、Scalaが目指しているのはファンクショナルプログラミングの完全なサポートなので、そのパラダイムの実現のために多くのクラスが必要になっているのだと思います。
ランタイムが大きいのならばProguardで削ればいいと思われますが、すべては使わないにしろ必要だからstdlibに入っているのであって、もちろん削れることは削れると思いますが、よりScalaらしいコードを書くとサイズが大きくなるのは仕方ないと思います。
さて、そんなstdlibですが、変更するのは簡単なので、試しにstdlibの中の適当なファイルを開いて自分の好きな拡張関数を追加して、コンパイルして実行してみましょう。
// libraries/stdlib/src/kotlin/text/Strings.kt
public inline fun String.hello(): String = "Hello World"
追加したら ant runtime
を実行するだけです。成果物は dist
以下に吐かれるので、早速実行してみましょう。
$ dist/kotlinc/bin/kotlinc
Type :help for help, :quit for quit
>>> "".hello()
Hello World
簡単ですね。
kotlincとは
他のコンパイラがそうであるように、kotlincもLexer analyzerがあって、Parserがあって、Code generatorがあり、それぞれ以下のように対応しています。
Lexcal analyzerは JFlex - The Fast Scanner Generator for Java というLexical analyzer generatorを使っていて Kotlin.flex
という定義ファイルから _JetLexer.java
というLexerを生成しています。
Parserにも何らかのParser generatorが使われたりしますが、kotlincではIntelliJ platform SDKというフレームワークを使っていて、トークンに対して先読みをしてマークするというのをマニュアルで書いてASTを構築しています。

ASTからOptimizerが不必要なBoxingやNull checkやDead codeを削除したのち、Code generatorがコード生成を行います。
Code generatorにはGroovyやClojureやJRubyやIntelliJやEclipseやLombokなど、あらゆるところで使われている ASM が使われています。イメージしやすくするために具体例を出すと、ASMで "Hello World" を出力するコードは以下のようになります。
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC,
"main",
"([Ljava/lang/String;)V",
null,
null);
methodVisitor.visitCode();
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC,
"java/lang/System",
"out",
"Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("Hello World");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V");
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitEnd();
ほぼアセンブラに近いですね。JVMの仕様を理解していないと書くことができません。kotlincではラップしたクラス群を作っていて、多少書きやすくなっています。
Kotlinとモバイルアプリ開発
モバイルアプリ開発にKotlinは使えるのか、という最初の目的に立ち返ると、現状多少バグっているとはいえ言語としても、言語開発環境自体もそんなに悪くない、しかしアプリのコードを書くには少しオートコンプリートが遅いとか、Lint系のツールが充実してないなど、日々良いところ悪いところの新しい発見があり、私の意見は数日おきに変わっています。
特にJack & Jillの出現で突然全滅したりしないかと心配になりましたが、Jillはクラスファイルを読み込んでJackで解釈できる中間コードを出力できるみたいなので(それができないと既存のライブラリが全滅しますもんね…)、aptのサポートは時間がかかるかもしれませんが、Kotlinがまったく動かなくなるということはないと思います。
何か問題があったとしても、もし心からやっていくぞという気持ちがあれば、Kotlinはオープンで誰でもコントリビュート可能なので、いくらでもコードを速くしたり周辺ツールを作っていけると思います。私が調べたのはここまでなので、ここからはみなさんの目で確かめてみてください。