Help us understand the problem. What is going on with this article?

Kotlin スコープ関数の使い分け

More than 1 year has passed since last update.

まとめ

使い分けのポイントはたったの2つだけ!

  • 対象のオブジェクトのブロック内引数を it にしたいか」 or this にしたいか」
  • 戻り値を「自分で指定したいか」 or 「対象のオブジェクトにしたいか」

スクリーンショット 2019-08-02 16.14.30.png

はじめに

スコープ関数をプロジェクトで使用する頻度が増えてきたので、この機会に私なりの見解でまとめてみました。

繰り返しになりますが、意識するポイントは「引数」「戻り値」の2つだけです。

(let, run, also, apply それぞれの定義についてはまとめてコチラに載せました。)
(また、使用頻度が低いため with については省略をしました。)

引数

ポイントは、

「ブロック内の引数を、 it にするか this にするか」

です。
それぞれの特長は以下のようです。

it

  • 名前を変えられるので引数の意味がわかりやすくなる。
val user = User()
nameTxt.text = user.name
ageTxt.text = user.age.toString()
hobbyTxt.text = user.hobby

// data class User(
//     val name: String = "Taro",
//     val age: Int = 20,
//     val hobby: String = "Baseball"
// )
// nameTxt, ageTxt, hobbyTxtはそれぞれTextViewのid

これが、

User().also { person ->
    nameTxt.text = person.name
    ageTxt.text = person.age.toString()
    hobbyTxt.text = person.hobby
}

こうなる。
thisの方でもいけますが、感覚的にはこんな感じです。
(もっとわかりやすい例があると思いますが、すみません。)

this

  • thisを省略できる。
val toast = Toast.makeText(this, "message", Toast.LENGTH_SHORT)
toast.setGravity(Gravity.CENTER, 0, 0)
toast.setMargin(10.0f, 10.0f)
toast.show()

これが、

Toast.makeText(this, "message", Toast.LENGTH_SHORT).apply {
    setGravity(Gravity.CENTER, 0, 0)
    setMargin(10.0f, 10.0f)
    show()
}

こうなる。
toast が何回も表示されていたのが、スッキリしました。

以上より、
名前を決めたいなら it,

it.method1()
it.method2()

のようになるのであれば、this (の省略)を使って

method1()
method2()

とするのがいいのではないかと思います。

戻り値

ポイントは、

「戻り値を、 自分で指定したもの にするか 対象のオブジェクト にするか」

です。
感覚的には以下のようになります。

自分で指定する

xxxxx.let {
    aaaaa = it
    it.bbbbb
    ccccc(it) <- 戻り値はコレ(ccccc(it))になる
}

対象のオブジェクト

  ⬇ 戻り値はコレ(xxxxx)になる
xxxxx.also {
    aaaaa = it
    it.bbbbb
    ccccc(it)
}

スコープ関数を使い始めのときはこのことがわかりませんでした。。。
しかし、このことがわかってからは
「Nullableのオブジェクトはletを使って処理すると良い」
ということの意味がようやくわかりました。

練習問題(1)

※コードの意味は特にありません。

val intent = Intent(this, SecondActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addCategory("sample")
startActivity(intent)

まとめの表に従うと、
まず、戻り値は必要でないので、対象のオブジェクトにしましょう。
引数はどちらでもいいと思いますが、個人的にはthisですかね。
よって、applyを使います。

Intent(this, SecondActivity::class.java).apply {
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    addCategory("sample")
    startActivity(this)
}

練習問題(2)

※こちらも特にコードに意味はありません。

val contents = getContents()
val master =
    if (contents != null) {
        println(contents)
        contents
    } else {
        throw Exception()
    }

private fun getContents(): String? = "Contents"

この場合は戻り値が必要なので、自分で指定できる方を選択します。
引数はthisだと内容がわかりにくくなるので、it
よってletを使用します。すると、以下のように記述できます。

val master = getContents()?.let { contents ->
    println(contents)
    contents
} ?: throw Exception()

どうでしょうか?かなりスッキリ書けたのではないでしょうか。
スコープ関数万歳!(エルビス演算子の効果もありますが。)

定義(まとめ)

以下のようにまとまって定義が載ってるサイトが少なかったので、比較しやすいようにまとめて載せました。是非、感覚的にイメージを掴んだ後にコードに立ち戻って定義を理解しましょう。

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

public inline fun <T, R> T.run(block: T.() -> R): R = block()

public inline fun <T> T.also(block: (T) -> Unit): T {
    block(this)
    return this
}

public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}

おわりに

Kotlinのスコープ関数使いたがり症候群でちゃう。

参考

https://qiita.com/ngsw_taro/items/d29e3080d9fc8a38691e

yasuX
Golang
sorich
SORICHはWebシステム開発を主軸に、デザイン・Web制作・アプリ開発・IoTまで、クライアントの幅広いニーズに対応する技術者集団です。
https://www.sorich.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away