くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

スコープ関数について

kotlinにはスコープ関数というものがあります。

あるインスタンスに対して、連続して処理をするような場合、スコープ関数を使えばコードを簡潔にすることができます。(Lamba形式で処理をおこないます)

スコープ関数にはletrunwithapplyalsoの5種類あります。

それぞれ説明しようと思います。

let

まずはletについてです。

letは個人的に1番使っているので、最初に説明します。

どんなときに使うかというと、nullチェックで行うことが多いです。

val test = "test"

val str = test.let {
    it.toUpperCase()
}

print(str)

上記のprintlnで表示されるのはstrがnullではない場合のみです。また、itはtestを表すレシーバーです。

Lambaの引数はitでitはスコープ関数で使わないといけません。また、戻り値として利用できます。

run

runはletとほとんど変わらないですが、レシーバーをthisにする必要があります。

val test = "test"

val str = test.run {
    this.toUpperCase()
}

print(str)

ただ、runはExtensionとして使用するパターンとExtensionとして使用しないパターンの両方で使えます。

val hexNumberRegex = run {
    val digits = "0-9"
    val hexDigits = "A-Fa-f"
    val sign = "+-"

    Regex("[$sign]?[$digits$hexDigits]+")
}

for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) {
    println(match.value)
}

上記はT.runのように使わずにrun {}というように使っています。

なので、runはオペレーション(操作)だけでなく、初期化のためにも使えます。

with

withに関して、働きはrunと同じです。そのため、withはあまり見かけることは無いかもしれません。

特徴としては、Extensionとして使用しないパターンです。

withは通常の関数のように呼び出し、2つの引数を取ります。第一引数にレシーバ、第二引数にラムダ式を記述しましょう。レシーバーはthisです。

レシーバーのthisは省略できます。

val test = "test"

val str = with(test) {
    this.toUpperCase()
}

print(str)

apply

ここからはlet・run・withとは考え方は同じですが、用途が異なります。

applyはプロパティを設定するために使用します。

val book = Book().apply {
    price = 827
    author = "AAAAA"
}
println(book.author)

上記のコードはBookインスタンスを生成したらプロパティを設定するコードです。

apply関数のラムダ式の中では、レシーバ(ここではBookインスタンス)をthisで参照することもできますが、このthisは省略することが可能です。

戻り値はプロパティが設定されたレシーバになります。(ここではBookインスタンス)

also

alsoはapplyと似た使い方ですが、明確に違う点があります。

alsoの中のラムダ式で、Bookインスタンスは「it」で参照されていて、省略不可です。

val book = Book().also {
    it.price = 827
    it.author = "AAAAA"
}

上記のitは省略できません。

まとめ

ここまでスコープ関数を説明しましたが、全てを使うことはほとんどないかもしれません。下記にまとめますが、用途に応じてしっくりくるものを使ってみてください。

レシーバー 戻り値 用途
let it 指定可能 オペレーション
run this 指定可能 オペレーション
with this 指定可能 オペレーション
apply this 対象オブジェクト プロパティ指定
also it 対象オブジェクト プロパティ指定

追記

レシーバが何を表しているかをきちんと理解しないとなぜ?と思うことが発生するかもしれません

例えば、Fagementでダイアログを表示する際を例に挙げます

private fun showCustomDialog() {
    CustomDialog().apply {
        arguments = Bundle().apply {
            putString("title", requireContext().getString(R.string.title_notifications))
        }
    }.show(parentFragmentManager, "customDialog")
}

上記の例はFragmentの中でDialogFragmentを継承したCustomDialogを表示させる処理です

この例だとExceptionが発生します。なぜでしょうか?requireContext()でcontextが取得できないからです

requireContext()の処理を参照すると、Context context = this.getContext();と行い、contextを取得していますが、thisはapply関数なので、CustomDialogになります。 そして、CustomDialogはアタッチされていないためcontextがnullになりExceptionが発生します。 なので、次のようにする必要があります(今回はHomeFragmentというフラグメントのクラスで行われている)

private fun showCustomDialog() {
    CustomDialog().apply {
        arguments = Bundle().apply {
            putString("title", this@HomeFragment.getString(R.string.title_notifications))
        }
    }.show(parentFragmentManager, "customDialog")
}

もしくはお勧めではないですが、CustomDialog().applyの部分をCustomDialog().alsoに変えてもクラッシュしません

参照

kotlinlang.org

pouhon.net