読者です 読者をやめる 読者になる 読者になる

Kotlin(かわいい)

この記事はAizu Advent Calendar 2015の5日目(@misoton665)のものです。www.adventar.org
前の方 @plasma_effectorさん: constexpr多倍長整数 · GitHub
次の方 @himaaaattiさん: H8マイコンのエミュレータつくったはなし - ひまわり

Kotlinとは

KotlinはIntelliJ IDEAで有名なJetBrains社が開発しているプログラミング言語です。JetBrainsはこの言語を開発する目的の一つとして、Androidで利用されることを挙げていて、さらにAndroidStudioでプラグインをインストールすればすぐにKotlinを試すことができます。詳細はこちら-> Getting started with Android and Kotlin

Kotlinのチャーミングポイント

Kotlinの良いところをAndroidアプリ開発を例に見ていきます。

拡張メソッドで気がきくKotlin

Kotlinには、既存のクラスに後からメソッドを付け加える事ができる仕組みがあります。これによって、何回も書く冗長なコードを省略して短くするようなメソッドを簡単に定義できます。
Activityを起動する処理を例にして見てみましょう。これはJavaのコードです。

Intent intent = new Intent(this, NextActivity.class).setAction(ACTION_VIEW);
startActivity(intent);

このコードで重要な部分は一行目の"NextActivity.class"の部分だけで、他の部分はどのActivityからどのActivityを起動するにしてもほぼ同じコードになります。
Javaでこの記述を省略して書こうと思うと、Activityクラスのサブクラスとして自分で新しくクラスを定義して、そこに省略するためのメソッドを定義して、さらに全てのActivityのスーパークラスを自分で新しく定義したクラスに書き換えなければなりません。面倒臭い。

Kotlinでは以下のように拡張メソッドを書けば終わりです。

fun <T: Activity> Activity.startActivity(classRef: KClass<T>, bundle: Bundle? = null, finishFl: Boolean = false) {
    val intent = Intent(this, classRef.java).setAction(Intent.ACTION_VIEW)
    bundle?.let {
        intent.putExtra("args", bundle)
    }
    startActivity(intent)
    if (finishFl) {
        finish()
    }
}

この拡張で、先ほどの処理はこのように書く事ができます。

startActivity(NextActivity::class)

これで無駄な記述はなくなりましたね!!かわいい!!!

さて、ここでもう一つ重要なのは、拡張メソッドを利用するのに必要なのは拡張メソッド自身の定義のみ、という点です。
Javaでは拡張済みのクラスを継承するように明記しなければなりませんでした。Kotlinは継承元のスーパークラスメソッドを拡張するので、継承するクラスを変える必要はありません。
また、拡張メソッドはそのスコープを限定することができるので、Activityの起動のようにどこでも使うものはスコープを広く、限定的な処理を拡張したい場合にはスコープを狭くすることで"後付け神クラス"が作られるのを避けることができます。

健康的なKotlin

KotlinにはOptional型という、nullをできるだけ使わない、使うとしても安全に使おう、という仕組みがあります。
実は先ほどのコードで、もうそのOptional型が使われています。

bundle: Bundle? = null

この"?"が付いているものがOptional型で、KotlinではOptional型で定義した変数のみ、nullを代入するのが許されています。つまり、Optional型でない通常の変数を定義して利用する時には、そもそもnullを代入するのが不可能*1なのでnullについて考える必要がない、ということになります。
さらに、このOptional型を参照するときには必ずnullチェックを行う必要があるので、全てOptional型で定義してしまってもはや意味がない、という状況にはなりません。
Optional型のnullチェックは、上の例で

bundle?.let {
    intent.putExtra("args", bundle)
}

この部分です。この場合は、bundleがnullではない時にintentのExtraにbundleを追加する、という処理になります。letはKotlinの全てのオブジェクトに自動的に追加されるメソッドです。ただ渡された処理(この場合は"intent.putExtra("args", bundle)")を実行するだけのメソッドですが、Optional型と組み合わせると効果的に使う事ができます。

曲がった事が嫌いなしっかり者のKotlin

Kotlinは再代入可能な変数をvar、再代入不可能な変数(定数)をvalで定義しますが、設計上どちらでもいい場合にはvalを使うのが一般的に良いとされています。というのも、varで宣言すると、いつどこで再代入されるのか分からないので、その変数の振る舞いを予想するのが困難になってしまい、予期せぬバグ*2の原因になることがあるからです。
Javaでもfinal修飾子をつけることである程度対策することは可能ですが、Android開発をする上で避けて通れないあるものはfinalをつける事が困難です。それはViewです。ViewはActivityがCreateされたときに生成されるので、onCreate以後にfindViewByIdによって変数に結び付ける必要があります。finalな変数はフィールドの宣言文かコンストラクタで初期化されなければならないため、Viewを保持するためのフィールドをfinalにするのは難しい*3です。
しかし、KotlinであればDelegateという仕組みを使うことで再代入不可能な変数として宣言することが可能です。

fun <T : View> Activity.bindView(resId: Int, clickListener: View.OnClickListener? = null): BindViewDelegate<T> {
    return BindViewDelegate(this, resId, clickListener)
}

class BindViewDelegate<T : View>
(val activity: Activity, val resId: Int, val clickListener: View.OnClickListener? = null) {

    private var value: T? = null

    @Suppress("UNCHECKED_CAST")
    fun newValue(resId: Int): T? {
        return activity.findViewById(resId) as? T
    }

    fun initValue(): T?{
        value = newValue(resId)
        clickListener?.let {
            value?.setOnClickListener(clickListener)
        }

        return value
    }

    operator fun getValue(thisRef: Any, prop: KProperty<*>): T {
        return value ?: initValue()!!
    }

    operator fun setValue(thisRef: Any, prop: KProperty<*>, value: T) {
        this.value = value
    }
}

色々込み入ったことをしていますが、重要なのはBindViewDelegate.getValueの部分です。

return value ?: initValue()!!

何をしているかというと、valueがnullでなければその値を、nullであればinitValue()を実行してその戻り値をOptionalを外して返すということをしています*4。これによって、初めて変数にアクセスするタイミングで初期化をするようになります。
Delegateは英語で委譲という意味で、オブジェクトの操作の一部を他のオブジェクトにまかせる仕組み、もしくはその操作を担うクラスの事です。例のBindViewDelegateはオブジェクトへの代入と参照を代わりに行うようなDelegateになっています。このDelegateを利用するには以下のように記述します。

val sampleButton: Button by bindView(R.id.main_sample_button, clickListener = this)

投稿時現在、はてぶにKotlinのシンタックスハイライトが付いていないのでわかりにくいですが、byというのがDelegateを適用するキーワードになっています。このような定義をしてもsampleButtonの型はあくまでButtonのまま、代入と参照の振る舞いだけをBindViewDelegateに委譲した変数になります。これでViewも再代入不可能な変数で定義する事ができました!!かわいい!!!

おわりに

Kotlinは現在1.0betaが公開されていて、正式なバージョンは年内か年始にリリースされる(らしい)です。私はKotlinの事を半年程前に初めて知ったのですが、初回のリリースから現在までにかなりの多くの仕様変更と機能追加があったそうで、これからもどんどん進化していく可能性があります。まだ実案件でKotlinを使う例は多くないですが、これから増えていくことを期待しています。もしこの記事や公式ページで興味を持たれたのであれば、一度触れてみてはいかがですか?

*1:Optional型でないものにnullを代入しようとするとコンパイルエラーが出ます。

*2:バグはいつでも予期しないものですが

*3:不可能?

*4:"?:"でOptional型のnullチェックをしています