Kotlinの気持ちよさ

kotlin_logo

こんにちは、pairs事業部Androidエンジニア Kotlinおじさんこと愛澤(@lvla0805)です。プライベートではフルKotlinでアプリを開発しています。
現在pairsでは開発言語としてKotlinの導入を検討し、社内で勉強会を行っています。
今回はなぜKotlinなのかといったことをご紹介したいと思います。

kotlinとは

JVM, Android, ブラウザ向けの静的型付け言語です。
IntelliJ IDEAの開発元であるJetBrains社がオープンソースで開発しています。
Javaとの100%相互利用性を持ちながらもJavaには無いモダンな機能を提供してくれる気持ちいい言語です。

なぜKotlinを選ぶのか

Kotlinはとても気持ちがいい言語です。Javaで冗長になっていた処理を削減することが可能です。
以下に例を挙げます。

型推論

String name = "aizawa";
final User user = new User();

Javaでは変数を定義する際に型を宣言していました。その変数の型が何であるかは右辺を見れば明らかです。

var name = "aizawa"
val user = User()

とてもすっきりしました、気持ちが良いですね!
Kotlinではこのように型を省略し、代わりにvarまたはvalを用います。
varは変数、valは定数を表します。私は基本的にvalを使います。再代入されない安心感。

Null安全

人はなぜNullPointerExceptionを引き起こしてしまうのか…。
変数や返り値がnullを返すのか、引数にnullを受け付けるのかがひと目で判断できないからです。
人が悪いのでなくnullを意識できない環境が悪いのです。

NullableとNotNull

val nullable: String? = null    // OK
var notNull: String = null      // コンパイルエラー
notNull = nullable              // コンパイルエラー

Kotlinではすべての型でnullの代入が可能かが区別されます。
いわゆるOptionalですね、KotlinではNullableと呼ばれています。
Nullableにnullを代入することが可能ですが、NotNullには代入する事ができません。これは非常に良い制約です。

安全呼び出し

val nullable: List<String>? = null
nullable.add("foo")  // コンパイルエラー
nullable?.add("bar") // OK

Nullableのメンバ関数を呼び出したり、Nullableのプロパティ(≒Javaのフィールド)を参照する際には?.を利用して安全呼び出しを行う必要があります。
安全呼び出しはNullableがnullでなかった場合にその関数を実行し、nullだった場合にはnullを返却します。
おめでとうございます、NPEの恐怖から開放されました!気持ちいいですね!

nullの代替値

if(name != null) {
    textView.setText(name);
} else {
    textView.setText("未入力")
}

このようなコードを書いたことはありませんか?
Kotlinではエルビス演算子?:を用いて1行で記述可能です。

textView.setText(name?: "未入力")

エルビス演算子を使うとnullだった場合の代替値を手軽に指定できます。気持ちいいですね!

高階関数

Kotlinでは関数を第一級オブジェクトとして扱えるので、関数を引数・返り値として受け渡すことや変数への代入が可能です。
引数に関数を取る関数や、関数を返す関数のことを高階関数と呼びます。高階関数にはどのようなメリットがあるのでしょうか。

Obserevrパターン

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ...
    }
});

Androidアプリ開発ではこのような無名クラスの代入が頻出します。
しかしこのコード、長ったらしいと思いませんか?
この記述で注目すべきはクリック時に何を行いたいのかということだけです。
インスタンスの生成やオーバーライドの記述は本質的ではありません。
Kotlinではこれを高階関数を利用して本質的な記述に絞ることができます。

button.setOnClickListener { ... }

javaで6行書いていた処理を1行で書くことができました。とてもすっきりしていて気持ちが良いですね!

クロージャ

swipeRefleshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
    @Override
    public void onRefresh() {
        f(!isFetching) {
            fetch(0).doOnSubscribe(new Action0() {
                    @Override
                    public void call() {
                        isFetching = true
                    }
                }).doOnUnsubscribe(new Action0() {
                    @Override
                    public void call() {
                        isFetching = false
                    }
                }).subscribe(...);
        }
    }

});

listView.setOnScrollListener(new AbsListView.OnScrollListener() {
    ...

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if(firstVisibleItem + visibleItemCount >= totalItemCount && !isFetching) {
            fetch(totalItemCount).doOnSubscribe(new Action0() {
                    @Override
                    public void call() {
                        isFetching = true
                    }

                }).doOnUnsubscribe(new Action0() {
                    @Override
                    public void call() {
                        isFetching = false
                    }

                }).subscribe(...);
        }
    }

});

このようにPullToRefresh、ListView等のページネーションを実装することは多いと思います。
ユーザーの操作を伴って通信を行う場合、多重で通信が実行されないよう制御する必要があります。
Javaでは無名クラスから参照されるローカル変数はfinalまたはeffectively finalでなければならないという制約があるのでfieldとしてbooleanを宣言し、if文で制御しています。
可能な限りスコープの広い変数は避けたいものです。

var isFetching = false

swipeRefreshLayout.setOnRefreshListener {
    if(!isFetching) {
        fetch(0).doOnSubscribe { isFetching = true }
            .doOnUnsubscribe { isFetching = false }
            .subscribe (...)
    }
}

recyclerView.addOnScrollListener { view, firstVisibleItem, visibleItemCount, totalItemCount ->
    if(firstVisibleItem + visibleItemCount >= totalItemCount && !isFetching) {
        fetch(totalItemCount).doOnSubscribe { isFetching = true }
            .doOnUnsubscribe { isFetching = false }
            .subscribe (...)
    }
}

Kotlinの場合、無名関数から外のローカル変数の参照に制約はありません。
しかしどうでしょう?ローカル変数にしたとはいえ、まだこのisFetchingをここ以外で書き換えてしまう困った人がいるかもしれません。
クロージャーを使ってisFetchingを隠してしまいましょう。

private fun getFetchFunction(): (Int) -> Observable<Response> {
    var isFetching = false
    val fetchFunction = fun (offset: Int): Observable<Response> {
        return Observable.just(isFetching)
                .subscribeOn(Schedulers.io())
                .filter { !isFetching }
                .doOnNext { isFetching = true }
                .flatMap { request(offset) }
                .doOnNext { isFetching = false }
                .observeOn(AndroidSchedulers.mainThread())
    }

    return fetchFunction
}

順を追って見てみましょう。
getFetchFunctionですが、これはIntを引数に取りObservableを返す関数を返す関数です。
その中でIntを引数に取りObservableを返す関数としてfetchFunctionを宣言し、返却しています。
では実際に利用してみましょう。

val fetchFunction = getFetchFunction()

swipeRefreshLayout.setOnRefreshListener {
    fetchFunction(0).subscribe (...)
}

recyclerView.addOnScrollListener { view, firstVisibleItem, visibleItemCount, totalItemCount ->
    if(firstVisibleItem + visibleItemCount >= totalItemCount) {
        fetchFunction(totalItemCount).subscribe (...)
    }
}

おめでとうございます、スコープの広い変数から開放されました。気持ちいいですね!

総括

いかがでしょう、最後まで読んでくださったあなたはすでにKotlinの気持ちよさに魅了されているのではないでしょうか。
Kotlinには他にも多数の気持ちのいい機能があります。

  • スコープ関数
  • 拡張関数
  • 名前付き引数
  • デフォルト引数
  • データクラス
  • オブジェクト
  • スマートキャスト…

またの機会にKotlinの気持ちよさをご紹介したいと思います。

  • このエントリーをはてなブックマークに追加

エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!

Recommend

UIデザインをするうえでデザイナーが理解すべき3つのこと

iOSアプリのUX改善! FacebookのAsyncDisplayKitで60FPSのハイパフォーマンスなiOSアプリを作る