kotlinにはスコープ関数というものがあります。
あるインスタンスに対して、連続して処理をするような場合、スコープ関数を使えばコードを簡潔にすることができます。(Lamba形式で処理をおこないます)
スコープ関数にはlet
・run
・with
・apply
・also
の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
に変えてもクラッシュしません