この記事は、モバイルファクトリー Advent Calendar 2015 の 14 日目の記事です。
昨日は akihiro さんのiOSアプリのレイアウト事始め でした。
今日は Java との相互運用性を強く意識した JVM で動作する言語 Kotlin の紹介と、Kotlin の拡張関数という機能を取り上げて説明したいと思います。
TL;DL
- Kotlin はとてもかわいくて、素晴らしい言語
- Kotlin の拡張関数は static メソッドとして実装されている
- みんなで Kotlin 使おう
Kotlin とは ?
Kotlin (ことりん) は IntelliJ IDEA で有名な JetBrains 社が開発している Java との相互運用性を持ったプログラミング言語です。オープンソースで開発されています。
Java の反省を活かして開発された言語で、Java と同等のプログラムがより安全に・短く・直感的に記述できることが特徴です。
日本語の小鳥に響きが似ているため、かわいい言語と言われることがあります 1。
拡張関数とは ?
ここで言う拡張関数とは、既存のクラスに対して後からメソッドを追加することを指します。拡張メソッド 2 や、エクステンションと呼ばれることもあります。
たとえば、Ruby のような動的言語だと、一度定義したクラスに対してでも簡単にメソッドを追加することができます。
class AdventCalendar
def year
2015
end
def month
12
end
def to_s
[year, month, day].join('/')
end
end
class AdventCalendar
def day
14
end
end
calendar = AdventCalendar.new
p calendar.to_s #=> 2015/12/14
しかし、静的言語においては、一度定義したクラスの定義は、後から変更を加える事ができないことが ほとんど です。これを可能にするのが拡張関数です。
もちろん、Kotlin から Java のクラスとして定義したクラスを書き換えることはできません。そのため Kotlin では、既存のクラスに対してのメソッドを別途定義し、見かけ上はメソッドとして呼んでいるように見える 実装がされています 3。
Kotlin での拡張関数とその実装
Kotlin では、既存のクラスに対して以下のように拡張関数を追加することができます。
たとえば Kotlin の数値に対して、その数値だけ繰り返す処理 times
を追加する場合を考えてみます。
@file:kotlin.jvm.JvmName("Main")
fun Int.times(callback: (Int) -> Unit) {
repeat(this) { callback(it) }
}
fun main(args: Array<String>) {
3.times { println(it) }
}
times
は引数に実行する処理 callback
を受け取り、自身の数値回数繰り返し callback
を呼び出します。repeat は times
と同じ動作をする関数で、Kotlin の標準関数です。
上記 Main.kt
をコンパイルすると *.class
ファイルが生成できます。Kotlin のランタイムに依存しているため、クラスファイルは直接 Java では実行できませんが、kotlin
コマンドで実行することができます。
$ kotlinc -version
info: Kotlin Compiler version 1.0.0-beta-2423
info: PERF: INIT: Compiler initialized in 517 ms
$ kotlinc Main.kt
info: PERF: INIT: Compiler initialized in 561 ms
info: PERF: ANALYZE: 1 files (9 lines) in 660 ms
info: PERF: GENERATE: 1 files (9 lines) in 92 ms
$ kotlin Main
0
1
2
$ ls
META-INF
Main$main$1.class
Main.class
Main.kt
せっかくなので、デコンパイルして実装を見てみましょう。
$ jad Main.class
Parsing Main.class...Parsing inner class Main$main$1.class... Generating Main.jad
Main.jad
が生成されたのでそれを見てみます。
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Main.kt
import kotlin.Unit;
import kotlin.io.ConsoleKt;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.internal.Intrinsics;
import kotlin.jvm.internal.Lambda;
public final class Main
{
public static final void times(int $receiver, Function1 callback)
{
Intrinsics.checkParameterIsNotNull(callback, "callback");
int index = 0;
int i = $receiver - 1;
if(index <= i)
do
{
int it = index;
callback.invoke(Integer.valueOf(it));
Unit _tmp = Unit.INSTANCE;
if(index == i)
break;
index++;
} while(true);
}
public static final void main(String args[])
{
Intrinsics.checkParameterIsNotNull(args, "args");
public static final class main._cls1 extends Lambda
implements Function1
{
public volatile Object invoke(Object obj)
{
invoke(((Number)obj).intValue());
return Unit.INSTANCE;
}
public final void invoke(int it)
{
ConsoleKt.println(it);
}
public static final main._cls1 INSTANCE = new main._cls1();
}
times(3, (Function1)main._cls1.INSTANCE);
}
}
以下の行より、Kotlin では、拡張関数が static メソッドとして実装されており、既存のクラスに対して一切の変更を加えずに実現されていることが分かります。
public static final void times(int $receiver, Function1 callback)
つまり、拡張関数内では private なメンバを呼ぶことが許されません。
実際に times
の処理を行っているのは以下の所です。元の Kotlin のコードでは repeat 関数を読んでいるだけですが、inline
アノテーションが付与されているため、処理内容が times
内にインライン展開されています。
Intrinsics.checkParameterIsNotNull(callback, "callback");
int index = 0;
int i = $receiver - 1;
if(index <= i)
do
{
int it = index;
callback.invoke(Integer.valueOf(it));
Unit _tmp = Unit.INSTANCE;
if(index == i)
break;
index++;
} while(true);
Kotlin のクロージャも綺麗に Java 4 で扱えるように変換されていますね。綺麗に簡潔に記述できる Kotlin すごい!!
まとめ
拡張関数以外にも、Kotlin には便利な機能がたくさんあります。Java との相互運用性にかなり配慮されて作られていて、Java コードから Kotlin への自動変換も可能であるので、機会があればぜひ使ってみてください。
明日は、kazuman519 さんが Advent Calendar に新しい風を吹かしてくれます。期待ですね!!