GoogleがKotlinによるAndroid開発をサポートしたのでJavaから移行するためのTipsを書きます(その1)

f:id:tkow:20170529034323p:plain

こんにちは。teratail開発チームの草間(@tkow39)です。

先日のAndroid I/OでGoogleがAndroid Oの開発に対して、公式にKotlinをサポートすることを発表しました。今までもAndroid Studioのプラグインや直接Kotlinをインストールして、Androidアプリを開発することはできたのですが、あまりメジャーな選択肢ではありませんでした。しかし、じわじわ人気が高まってきていた昨今、今回のGoogle発表で、注目が集まっています。

私が1年ほど前にKotlinを触り始めた際は(Android Studiを2.3にバージョンアップした時にプラグインが使えなくなったり,JVMのバージョンをあげると主要なJSONライブラリが使えなくなったりと悲しい目にあったこともありましたが)「これは今まで触った中でも本当にすごい言語だ。今後はKotlinでAndroid開発していこう。」と感動したくらいだったので、この発表に対して「やっとそういう動きになって来たな」と感じました。

今回は、そんな今話題のプログラミング言語Kotlinについて書きます。

Kotlinとは?

まず、今回の発表でKotlinというプログラミング言語を初めて耳にした人もいるのではないでしょうか。KotlinはInteliJ IDEA などのIDEを開発しているJetBrains社が開発したJava Virtual Machine上で動くプログラミング言語です。このような言語はAlt Javaと呼ばれたりします。Javaとの互換性を保ちつつ、可読性の高いコードが書けるため、ここ1、2年の間にJavaの代わりにAndroid開発に使われるようになってきていました。

kotlinlang.org

KotlinはJavaの可用性を殺さず、Javaでは表現しにくかった処理を簡潔に書くことができます。また、Swiftと類似の言語仕様が多々あるため、Swiftを書ける人にとっては、Kotlinのハードルは低くなります

今回からAndroid開発でJavaからKotlinに乗り換えた際に私が経験したことなどを元に、今後Android開発でKotlinを使っていこうと思っているけど、何がそんなによくなるのか分からない方やKotlinを使ったことない方向けに、役立ちそうなtipsをネタ切れするまで不定期で書いて行こうと思います。

初回はAndroid開発におけるKotlinのプロジェクト設定と言語仕様の話からして行こうと思います。

ただし、現時点ではAndroid Studioで開発することを前提に記事を書いていますのでその他のIDEを使っている方に対しては、使えない機能があることをご承知おきください。

JavaのコードをKotlinに変換する

kolinをAndroid Studioで使い始めるのはとても簡単です。 Android Studioのjetbrainsのプラグイン集からKotlinを選択してインストールするとAndroid StudioでKotlinを利用することができるようになります

f:id:tkow:20170529033610p:plain

このとき、アクションコマンドウインドウからJavaのコードをKotlinに変換できるようになるので、Javaで用意したテンプレートファイルや既存のコードを使用した際でも、ある程度Kotlinのコードに自動変換することができます。

f:id:tkow:20170529033633p:plain

ただしこのとき、JavaにはNull許容型でないなどの原因で完全な変換がうまくいかないことがありますので、多少の修正が必要になることがあります。ここは多少面倒になりますが、原因を読み取りつつ手作業で直しましょう。

もし完全にKotlinのコードに直す自信がなければ、JavaのコードをKotlinのライブラリとして使用する方法もあるので、ライブラリ化してオブジェクトやクラスをKotlinから利用してみるのも一つの手だと思います。

直接.ktファイルをテンプレートから作成したい場合、今まではAndroid Studioの機能で.ktファイル用のテンプレートを自力で登録していくしかありませんでしたが、今後はGoogleが.kt用のテンプレートファイルを追加していってくれるかもしれません。

変数の宣言

varが変数, valが固定値になります。 代入される値が型推論できる場合は、宣言時の型指定を省略できますセミコロンは必要ありません型名末尾に?を入れることでNullable(null許容型)にできます

//Nullable型のInt
var number : Int? = 1000
//型推論でLong型に
val interval = 1000.toLong()

class内では、Javaと同様アクセサの指定もできます。 また,class内のプロパティはlateinitをつけると参照型のオブジェクトの遅延評価ができます。

class Hoge{
  private lateinit var lock: String
}

Singltonオブジェクト・Static なパラメータ

Kotlinの強力な仕様、objectは押さえておくべきでしょう。objectはクラス外・クラス内で宣言することができ,クラス外ではシングルトンなオブジェクトとして、クラス内ではStaticなパラメータとして使用することができます。例えばクラス外では以下のようにAPIにリクエストを送るようなシングルトンオブジェクトなどを作成してみます。FuelはKotlin用のHTTPクライアントライブラリで、中身は公式サポートされているJavaのHttpClientクラスのシンプルなラッパーになっているため、安心して使うことができます。

import com.github.kittinunf.fuel.*
import com.github.kittinunf.fuel.core.*
import com.github.kittinunf.result.Result
//中略
object APIClient{
    val url = "http://staging.api.example.jp"
    var action = "getDate"
    var header: MutableMap<String, String> = mutableMapOf()
    init{
         url += if(API_PATH.isNullOrBlank) API_PATH else "/api/"
    }
    fun getDate(query: List<Pair<String, Any?>> = listOf()){
        Fuel.get("${url}/${action}/", query).header(header).responseString { request, response, result ->
            println("Request" + request.toString())
            println("Response" + response.toString())
            println("Result" + result.toString())
        }
    }
}

これはstaging.api.example.jpのgetDateのエンドポイントにGETメソッドを送るためのシングルトンオブジェクトです。御覧のようにobjectのメンバには関数を設定することもでき、object内で定義された変数はオブジェクト内で参照できます。Kotlinでも当たり前に変数展開が使えます。オブジェクトのメンバはカンマではなく改行で判別されます。また、initを設定するとオブジェクト生成時の処理を定義できるため、設定されたパラメータを環境によって書き換えることができます。

このオブジェクトはメモリ上に移る時だけ初期化され、すべてのファイルはこのオブジェクトが破棄されていない限り、同一のオブジェクトを参照することを保証しています

補足になりますがKotlinのifは文ではなく、式評価なので、変数に戻り値を代入することができます。Kotlinでは可読性のため三項演算子は文法から排除されているので、三項演算子と同様の処理を実現したい場合はif else式を使います

このシングルトンオブジェクトのメンバには、

APIClient.getDate(listOf("date" to "today"))

のようにアクセスすることができます。(toはKotlinの予約語でMap型やPair型のオブジェクトを作成するときに使える汎用クラスのオブジェクトを作成します)

クラス内のobjectはstaticなインナークラスとして扱われます。例えば、

class Hoge{
   object APIClient{
      //略
   }
}

としてやると、APIClientのメンバへは、

Hoge.APIClient.url

のようにアクセスできます。ただしこれは、staticなインナークラスではなく、オブジェクト内のメンバをクラスのstaticなメンバとして扱いたい場合は、APIClientアクセス子が邪魔です。その場合、objectにcompanion修飾子をつけることでクラス内のstaticメンバとして扱うことが可能になります

class Hoge{
   companion object {
      //略
   }
}

// Hoge.urlでアクセス可能に

Click Listenerを簡略化して書く

kotlinのクロージャ(ラムダ式)は他の言語でも同じようなものが見られるように関数オブジェクトです。 なので、関数の引数として渡したり、変数に代入するということが可能です。 また、トレイリングクロージャーを使うことができます。 トレイリングクロージャーとは関数の最後の引数が関数オブジェクトの場合、Parentheses(丸括弧)外に出すことができる仕様のことです。これはコードの可視性をよくするための仕様です。実際にどう変わるか二つのコードを見比べてみます。

JavaでClickListenerにイベントを設定する際に即時的にインターフェースを継承した無名クラスオブジェクトを作成し、イベントをオーバーライドするという形式でした。Javaのコードは以下のようになります。

btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Intent intent = new Intent(getApplicationContext(), NextActivity.class);
        startActivity(intent);
        finish();
    }
});

Kotlinではイベント設定の際に、setListenerにOverload(型によって違う処理をする同名関数を設定できる仕組み)が存在し、関数を引数にすることでイベント登録することができます

btn.setOnClickListener{ view ->
    val intent = Intent(applicationContext ,NextActivity::class.java)
    startActivity(intent);
    finish();
};

以上の二つを比べてみてもだいぶ可読性がよくなったのではないでしょうか。上記の引数の型はsetOnClickListenerで暗黙的に定義されているので、必要ないことも注目に値するでしょう。

ちなみにKotlinでも無名クラスオブジェクトにインターフェース継承させる方法でイベント設定することもできます

btn.setOnClickListener(object :View.OnClickListener(){
    override fun onClick(v: View) {
            //省略
    }
});

上記の書き方は、自分でイベントリスナーを継承したインターフェースを自作した際に独自インターフェースの実装をオーバーライドさせる時などに使います。例えば、以下はボタン連打防止用のコードですがonClickListenerを継承したクラスの独自のメソッドを書き換えられるようにしたい時などに使えます。

import android.os.Handler
import android.view.View

abstract class CustomClickListener : View.OnClickListener{

    final override fun onClick(v:View) {
        if (v.isEnabled) {
            v.isEnabled = false
            onDoIt(v)
        }
        Handler().postDelayed({
            v.isEnabled = true
        }, 2000L)
    }

    abstract fun onDoIt(v:View)

}

btn.setOnClickListener(object :CustomClickListener(){
    override fun onDoIt(v: View) {
            //省略
    }
});

Label Jump・return@func・this@bind

(注: return@func・this@bindは公式の呼び方ではなく、機能を表すために便宜上筆者が項目分けしたものです。何か公式の呼び方があれば教えていただけると助かります。)

Kotlinの強力な機能として、ラベルアクセスがあります。これを使いこなすと、あるブロックスコープ内から任意の外部スコープに強制的に戻り値を返すことができます。こちらはドキュメント(

Returns and Jumps - Kotlin Programming Language

)と同じ例になりますが、通常二重目のループでブレイクすると、二重目のループまでしか終了しませんが、loop@というラベルを付けることで、break後の呼び戻しを内側のブロックではなく外側のブロックに変更することができます

loop@ for (i in 1..100) {
    println(i)
    for (j in 1..100) {
        if (j ==2) break@loop
    }
}
//出力は1しか表示されていない

関数の戻り値にラベルを指定するとそのラベルが挿入されている関数に戻り値を返すことができます。これを利用することで、クロージャや内部関数の戻り値を任意の関数の戻り値に変更することができます。例えば、

fun hoge(id:Int,text:String) label@{
    val message = text?.let  {
       if(it=="processing") return@label it
       if(it=="save") return@let getString(id) //idから何らかの値を返す疑似コード
       "done"
    }
  //何らかの後続処理
}

という、何秒かに一回hogeを呼び出すとすると、処理のプロセスに応じてメッセージに応じてreturn先の関数を変える関数を定義してみます。 実は、labelのデフォルト値は関数名に設定されているので、上記の例のように、@letでクロージャ呼び出し元のlet関数を指定できます。(ラベルを付けると上書きされてアクセスできなくなります。) さらにKotlinは式の最終評価値を自動的に戻り値として返すので、クロージャ内での最終評価値を戻り値にしたい場合は、"done"のようにreturnを省略できます。今回の例は同期的なメソッドですが、非同期処理でこれをうまく使うとかなり複雑な処理を簡単に書けるようになります。

最後のthis@bindですが、Android開発で最も面倒が起きやすいthisの参照先を指定できます。Javaで開発を行う際Activity内のListenerを無名クラスで引き継ぐ際に、thisがイベント設定先のオブジェクトを指してしまうことにより、イベントハンドラにActivityを引き渡したい場合は変数にインスタンスの実態を保持しておくなどの、ちょっとした工夫が必要でした。Kotlinのthis@bindは、そのthisが指すクラスのインスタンスを内包している外部のスコープのクラス名を指定することで外部のクラスのインスタンスをthis@(クラス名)で呼び出すことができます

class HogeActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        var fuga = findViewById(R.id.fuga) as ImageView
        fuga.setOnClickListener{ view ->
            val to = Intent(this@HogeActivity,NextActivity::class.java)
            startActivity(to)
            finish()
        }
    }
}

以上の例では、本来thisはfugaで表されているImageViewを指していましたが、this@HogeActivityを指定したことで、fuga.setOnClickListenerを呼び出したHogeActivityのインスタンスをfugaの内部からアクセスすることができています。また、this@HogeActivityのあとに.チェーンを続けることで、HogeActivityクラスのインスタンスのメンバにアクセスすることもできます

Gradleで外部ライブラリをインポートする

Kotlinのライブラリのインポート方法はJavaとほぼ一緒です。build.gradleに環境に合わせた設定を行います。また、Javaのライブラリは問題なくKotlin側のコードにimportできます

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'org.apache.commons:commons-lang3:3.3.2'//Javaライブラリ
    compile 'com.squareup.picasso:picasso:2.5.2' //Kotlin用ライブラリ
    compile 'com.android.support.constraint:constraint-layout:1.0.0-beta4'
    compile 'junit:junit:4.12//Javaライブラリ
}

以上からJava、Kotlin双方のファイルが開発プロジェクトに混ざっていても大丈夫なことがわかります。それぞれのオブジェクトを相互に渡したい場合は、それぞれのフォーマットにそってライブラリのオブジェクトを参照することができます

さいごに

皆さん、いかがだったでしょうか?まだあまりKotlinを知らなかった方もKotlinいいねってなってもらえたら幸いです。また、TeratailチームではKotlinのHandonや、入門者の会なども不定期に開催しています。直近の6/26のイベントには「Kotlinスタートブック」の著者の長澤太郎さんもいらっしゃいます

KotlinのLT会!Kotlin入門者の集い - 参加者・申込者一覧 - connpass

現在6/26のKotlin入門者の会は申し込みは定員数を超えてしまっていますが、今後もKotlinをこれから使い続けていこうと思っている方はぜひ今後のイベントもチェックしてみてください。ここまでお読みいただきありがとうございました!今後ともteratailブログをよろしくお願いいたします。

www.jetbrains.com