Kotlin - apply、also、run、let

公開日:2019-11-06 更新日:2019-11-06
[Kotlin]

1. 概要

apply、also、run、let は、任意のラムダ式をメソッドチェーンにして実行できます。
各メソッドの違いは、ラムダ式内でのレシーバー(呼び出し元オブジェクト)の参照方法と戻り値です。
takeIf やエルビス演算子などと併せて使うと、1行で様々な処理を行うことができます。

                       // レシーバー  戻り値
obj.apply { ラムダ式 } // this        obj
obj.also  { ラムダ式 } // it          obj
obj.run   { ラムダ式 } // this        ラムダ式の戻り値
obj.let   { ラムダ式 } // it          ラムダ式の戻り値


2. apply

apply は「適用する」と言う意味なので、レシーバー(呼び出し元オブジェクト)にラムダ式の内容を適用すると言う意味で使うと良いかもしれません。
例えば、オブジェクト生成時に apply で初期化処理など。
val list1 = arrayListOf(3, 1, 2).apply {
    add(0)   // this. を省略しています
    sort()   // this. を省略しています
}
println(list1) // [0, 1, 2, 3]

下記は、上記と同じ結果になります。
メソッドを実行する際にレシーバーが不要となり、可読性の向上、コード量の削減、
誤って別のオブジェクトのメソッドを実行することがなくなります。
val list1 = arrayListOf(3, 1, 2)
list1.add(0)
list1.sort()
println(list1) // [0, 1, 2, 3]

apply のあとに他のメソッドを繋げて実行することもできます。
class Test {
    var value1:Int = 0
    var value2:Int = 0
    fun add():Int = value1 + value2
}

val result = Test().apply {
    value1 = 1
    value2 = 2
}.add()

println(result)  // 3


3. also

also と apply との違いは、レシーバーの参照方法(it or this)です。
also は、レシーバーをラムダ式の引数となるような処理に使うと良いかもしれません。
例えば、メソッドチェーンの途中でログ出力を行う場合は、apply よりも also が良いと思います。

val result = arrayListOf(1, 2, 3).also {
    println(it.max()) // 3
    println(it.min()) // 1
}.sum()

println(result)  // 6


4. run

apply と run との違いは、戻り値です。
run はラムダ式の結果を返します。
指定されたラムダ式を、レシーバーのメソッドとして考えて使うと良いかもしれません。

val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val avg = list.filter { it % 2 == 0 }.run {
    sum() / count()  // average()
}
println(avg) // 6


5. let

let と run との違いは、レシーバーの参照方法(it or this)です。
let は、レシーバーをラムダ式の引数として実行する際に使うと良いかもしれません。
そのため、let のレシーバーは一時的な値であることが多い気がします。

例えば以下の例では、File オブジェクトと length() は、ラムダ式の引数(it)として一時的に使用され、破棄されています。
val size = File("c:/test.png").length().let {
    "%.2fMB".format(it.toFloat() / 1024 / 1024)
}
println(size) //2.49MB

例2
val s = "a = 100"
val v = s.indexOf('=').let { s.substring(it + 1).trim() }
println(v) // 100