【Kotlin】スコープ関数って聞いたことあるけどどんな関数なの?

Kotlin にはスコープ関数という便利な関数があります。

スコープ関数を使えばどう便利なのかやソースをどう簡潔に書けるのかといったことを今回のブログではお話ししようと思います。

ご興味のある方はぜひ最後まで読んでくださいね。

スコープ関数ってどんな関数?

スコープ関数とはレシーバーオブジェクトを引数に取りそのオブジェクトを操作するために使用される関数のことです。

スコープ関数はより簡潔なコードを作成するのに役立ち、コードの読みやすさを向上させ、レシーバーオブジェクトのスコープを明確にするのにも役立ちます。

レシーバーオブジェクトとは下記コードの "Hello, World!" にあたる部分のことです。also 関数ではスコープ関数のスコープ内では it でレシーバーオブジェクトを受け取ることができます。

    "Hello, World!".also {
        println("message length is ${it.length}")
    }

どんなスコープ関数があるの?

スコープ関数は全部で5種類あります。スコープ関数を一覧化したものが下記になります。

No.スコープ関数何を返すかスコープ内で
オブジェクト
へのアクセス
備考
1let最後の式itnullセーフな呼び出しも可能
2run最後の式this 
3with最後の式this 
4apply自身this 
5also自身itnullセーフな呼び出しも可能

let関数

コード例は下記のとおりです。

    val nullableString: String? = "Hello, World!"
    nullableString?.let {
        println(it.length) // 13
    }

nullableStringはNULLが可能な文字列で宣言しています。そのため、?. で null でない場合のみスコープ内のコードを実行しています。let 関数の場合はレシーバーオブジェクトは it で取れるので it.length で文字列の長さを取得しています。

let 関数を使えばNULLが可能な変数を非NULLな変数に変換することができます。下記が参考コードです。

    val name: String? = null
    val length = name?.let { it.length } ?: 0

name はNULLが可能な文字列で null が代入されています。?. で name が null でない場合は文字列の長さが返されますが、null の場合は 0 を返しています。つまり、非NULLな値を返しています。

run関数

コード例は下記のとおりです。

    val numbers = mutableListOf(1, 2, 3)

    val sum = numbers.run {
        add(4)
        add(5)
        sum()
    }

    println(sum) // 15
    println(numbers) // [1, 2, 3, 4, 5]

numbers は変更可能なリスト(ミュータブルなリスト)で [1, 2, 3] を代入しています。run 関数の中で 4 と 5 をレシーバーオブジェクトに追加しています。その後、sum() で合計を返し sum に代入しています。その結果、sum が 15 、nubmers が [1, 2, 3, 4, 5] を出力しています。

with関数

コード例は下記のとおりです。

    val person = Person("Alice", 25)

    val result = with(person) {
        age += 5
        "In five years, $name will be $age years old."
    }

    println(result) // In five years, Alice will be 30 years old.
    println(person.age) // 30

person に名前が Alice と年齢が 25 の Person クラスを代入しています。with 関数の中で年齢を 5 歳足しています。これは person.age += 5 と同じことです。その後、"In five years, $name will be $age years old." を result に代入しています。これは "In five years, ${person.name} will be ${person.age} years old." と同じことです。その結果、result と person.age を出力しています。

apply関数

コード例は下記のとおりです。

    val textView = TextView(context).apply {
        layoutParams = LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT
        )
        text = "Hello, World!"
        textSize = 16f
        setTextColor(ContextCompat.getColor(context, R.color.black))
    }

これは下記コードと同じことです。

    val textView = TextView(context)
    textView.layoutParams = LinearLayout.LayoutParams(
                 LinearLayout.LayoutParams.MATCH_PARENT,
                 LinearLayout.LayoutParams.WRAP_CONTENT
             )
    textView.text = "Hello, World!"
    textView.textSize = 16f
    textView.setTextColor(ContextCompat.getColor(context, R.color.black))

apply 関数を使えばコードが簡潔になっているのが分かります。

also関数

コード例は下記のとおりです。

    val message = "Hello, world!"

    val result = message
        .also { println("message length is ${it.length}") }
        .toUpperCase()

    println("result: $result") // result: HELLO, WORLD!

message に "Hello, world!" を代入しています。also 関数を使って文字列の長さを出力しています。also 関数はレシーバーオブジェクトをそのまま返すため .toUpperCase() で大文字に変換しています。その結果、result: HELLO, WORLD! を出力しています。

最後の式を返すとは?

スコープ関数の中で最後の文が戻り値となるのは、let、run、with の3つです。他のスコープ関数で最後の式を返す場合は return を用いて明示的に戻り値を指定する必要があります。

    val person = Person("Alice", 25)

    val result = with(person) {
        age += 5
        "In five years, $name will be $age years old." // 最後の式⇒ここがスコープ関数の戻り値となる
    }

    println(result) // In five years, Alice will be 30 years old.
    println(person.age) // 30

スコープ関数の中で最後の文 "In five years, $name will be $age years old." が戻り値となっています。

自分自身を返すとは?

スコープ関数の中で、スコープ関数実行後元のオブジェクトを返すのは apply 関数と also 関数です。apply 関数はレシーバーオブジェクトを返し、also 関数はレシーバーオブジェクトを引数として渡すクロージャー内で使用されたレシーバーオブジェクトを返します。

apply 関数のコード例は下記のとおりです。

    val textView = TextView(context).apply {
        layoutParams = LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT
        )
        text = "Hello, World!"
        textSize = 16f
        setTextColor(ContextCompat.getColor(context, R.color.black))
    }

TextView(context) の layoutParams、text、textSize、setTextColor を変更をしたもの(レシーバーオブジェクトそのもの)が返されます。

also 関数のコード例は下記のとおりです。

    val message = StringBuilder().append("Hello")
        .also { builder -> // builder をレシーバーオブジェクトの引数としています
            builder.append(", Kotlin!") // クロージャー内
        } // "Hello"ではなく"Hello, Kotlin!"がレシーバーオブジェクトとして返します
        .toString()

    println(message) // "Hello, Kotlin!"

StringBuilder オブジェクトを作成し、"Hello"という文字列を追加しています。その後、also 関数を使用して、レシーバーオブジェクトを引数として渡すクロージャー内で、オブジェクトに", Kotlin!"を追加しています。そして、追加後のオブジェクトを返すために also 関数を使用しています。最後に、toString 関数を呼び出して、StringBuilder オブジェクトを文字列に変換し、変数 message に代入しています。

おわりに

いかがでしたか?

スコープ関数を使えば便利になることや簡潔にコードが書けることが伝わりましたでしょうか?

スコープ関数を使いこなせたら Kotlin がさらに便利な言語になりそうです。

なんか Kotlin ってかっこいい!

最後までお読みいただきありがとうございました。

Google広告

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA