Kotlin 構文の高度な - スコープ関数とアプリケーション シナリオ

Kotlin 標準ライブラリには、let、run、with、apply などのいくつかの関数が用意されています。また、その唯一の目的は、オブジェクトのコンテキストでコード ブロックを実行することです。このような関数がオブジェクトに対して呼び出され、ラムダ式が提供されると、名前なしでオブジェクトにアクセスできる一時的なスコープが形成されます。これらの関数はスコープ関数と呼ばれます。
これらの関数
の共通点は、オブジェクトに対してコードのブロックを実行することです。
違いは、このオブジェクトがコード ブロック内でどのように使用されるか、そして式全体の結果が返されるかどうかです。

関数の実行と適用

run 関数: コンテキスト オブジェクトはラムダ式の受信者であり、ラムダ式を実行し、ラムダ式の結果を返します。

public inline fun <T, R> T.run(block: T.() -> R): R {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

apply 関数: コンテキスト オブジェクトはラムダ式の受信者であり、ラムダ式を実行してコンテキスト オブジェクトを返します。

public inline fun <T> T.apply(block: T.() -> Unit): T {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

run 関数と apply 関数の共通点は、コンテキスト オブジェクトがラムダ式の受信者として機能することですが、返される結果は異なります。run 関数はラムダ式の結果を返し、apply 関数はコンテキスト オブジェクト自体を返します。 。
使用シナリオ:
run 関数と apply 関数は両方とも、オブジェクトに値を割り当てるか、特定の操作を実行するものとして理解でき、どちらもオブジェクトの初期化に使用でき、また、頻繁に使用されます。
run 関数の戻り値はラムダ式の結果であるため、いくつかの (初期化) 操作を実行した後に戻り値が必要なシナリオでよく使用されます。
apply 関数の戻り値はコンテキスト オブジェクト自体であるため、チェーン呼び出しで使用して、呼び出しチェーン内の特定のリンクにあるオブジェクトに対して何らかの操作を実行できます。

例 1: 以下は初期化操作です。戻り値の要件はありません。run または apply 関数を使用できます。違いはありません。

binding.recyclerView.run {
    
    
  layoutManager = LinearLayoutManager(this)
  addOnItemTouchListener(RecyclerView.SimpleOnItemTouchListener())
}
或者
binding.recyclerView.apply{
    
    
  layoutManager = LinearLayoutManager(this)
  addOnItemTouchListener(RecyclerView.SimpleOnItemTouchListener())
}

例 2: 次のように、人物に対していくつかの操作を実行し、年齢を 10 歳増加させ、最後に増加した年齢を 10 歳ずつ定数に戻す必要があります。ここでは apply を使用できず、run のみを使用します。人物そのものですが、ラムダ式の結果である年齢の増加

data class Person(var name: String, var sex: String, var age: Int) {
    
    
  override fun toString(): String {
    
    
    return "姓名:$name , 性别:$sex , 年龄:$age"
  }
}

val user = Person("小慧", "女", 18)
val ageOlder = user.run {
    
    
  age += 10
  Log.e("时光流逝", "$name 的年龄增加了10岁")
  age
}

例 3: 以下のように、チェーン呼び出しを行い、途中で Person オブジェクトを変更し、最後に変更された Person オブジェクトを出力します。このとき、必要なのは person オブジェクトそのものであり、必要なものではないため、run ではなく apply を使用する必要があります。ラムダ式。式の結果

data class Person(var name: String, var sex: String, var age: Int) {
    
    
  override fun toString(): String {
    
    
    return "姓名:$name , 性别:$sex , 年龄:$age"
  }
}
fun String.logE(tag: String) {
    
    
  Log.e(tag, this)
}

val user = Person("小慧", "女", 18)
user.apply {
    
    
name = "小米"
sex = "男"
age = 20
}.toString().logE("人物信息")

機能付き

with function: ラムダ式の受信者を指定し、ラムダ式を実行してラムダ式の結果を返します。これは run 関数と非常によく似ています。

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

使用シナリオ: オブジェクトに対して特定の操作を実行した後に結果を取得します。セマンティクスはより強力です。使用シナリオは、値の計算にプロパティまたは関数が使用される補助オブジェクトを導入することです。
ケース: 上記のコードは、ユーザーを使用して ageOlder を計算する with 関数に変更できます。

val ageOlder = with(user) {
    
    
  age += 10
  Log.e("时光流逝", "$name 的年龄增加了10岁")
  age
}

機能させて、また機能させます

let 関数: コンテキスト オブジェクトはラムダ式のパラメータであり、ラムダ式を実行し、ラムダ式の結果を返します。

public inline fun <T, R> T.let(block: (T) -> R): R {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

また関数: コンテキスト オブジェクトはラムダ式のパラメータであり、ラムダ式を実行してコンテキスト オブジェクト自体を返します。

public inline fun <T> T.also(block: (T) -> Unit): T {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

let 関数と also 関数の共通点は、コンテキスト オブジェクトがラムダ式のパラメータ it として使用されることですが、返される結果は異なります。let 関数はラムダ式の結果を返し、 also 関数はコンテキストを返します。オブジェクトそのもの。

let 関数の使用シナリオ:
1. 呼び出しチェーンの結果で 1 つ以上の関数を呼び出します:

公式の例:

val numbers = mutableListOf("one", "two", "three", "four", "five")
val resultList = numbers.map {
    
     it.length }.filter {
    
     it > 3 }
println(resultList)
替换为:
val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map {
    
     it.length }.filter {
    
     it > 3 }.let {
    
     
    println(it)
    // 如果需要可以调用更多函数
} 

2.let は、null 以外の値のみを使用してコードのブロックを実行するためによく使用されます。null 以外のオブジェクトに対して操作を実行するには、安全な呼び出し演算子?. を使用し、 let を呼び出してラムダ式で操作を実行します。

公式の例:

val str: String? = "Hello" 
//processNonNullString(str)       // 编译错误:str 可能为空
val length = str?.let {
    
     
    println("let() called on $it")
    processNonNullString(it)      // 编译通过:'it' 在 '?.let { }' 中必不为空
    it.length
}

3. let が使用されるもう 1 つの状況は、コードの読みやすさを向上させるために、スコープが制限されたローカル変数を導入することです。コンテキスト オブジェクトの新しい変数を定義するには、デフォルトの変数ではなく、その名前をラムダ式パラメータとして指定します。

公式の例:

val numbers = listOf("one", "two", "three", "four")
val modifiedFirstItem = numbers.first().let {
    
     firstItem ->
    println("The first item of the list is '$firstItem'")
    if (firstItem.length >= 5) firstItem else "!" + firstItem + "!"
}.toUpperCase()
println("First item after modifications: '$modifiedFirstItem'")

also の使用シナリオ:
also は、コンテキスト オブジェクトをパラメーターとして受け取るいくつかの操作を実行する場合に便利です。オブジェクトのプロパティや関数ではなくオブジェクトを参照する必要がある場合、または外部スコープからの参照をブロックしたくない場合にも使用します。

公式の例:

val numbers = mutableListOf("one", "two", "three")
numbers
    .also {
    
     println("The list elements before adding new one: $it") }
    .add("four")

概要: let、run、with、apply の使用法は非常に似ています。多くの場合、これらは同じ意味で使用できます。具体的な使用方法の仕様は、個人の理解と会社の開発仕様に基づいています。
例:
run と apply は、通常、初期化操作 (代入操作に近い) に使用されます。apply はコンテキスト オブジェクト自体を返すため、apply は、チェーン内のオブジェクトが割り当てられて変更されるチェーン呼び出しでもよく使用されます。操作用。
with は一般に、オブジェクトに基づく計算や処理の結果を別のオブジェクトに代入するために使用されます。let とよく似ています。公式ドキュメントでは 3 つの使用法があります: 1.) コール チェーンの結果に対して 1 つ以上の関数を
呼び出す2.) 安全な呼び出し演算子を協力しますか?. null 以外の操作を実行します 3.) コードの可読性を向上させるために、スコープが制限されたローカル変数を導入します. プロパティや関数の代わりにオブジェクトを参照する必要がある操作にも使用されます
. これは apply と に非常に似ています難しい区別: apply のコンテキスト オブジェクトはラムダ式の受信者であり、 のコンテキスト オブジェクトもラムダ式のパラメータです。apply は主にコンテキスト オブジェクトの変数の代入計算に使用されますが、apply はコンテキスト オブジェクトの変数の代入計算にも使用されます。コンテキスト オブジェクト自体の操作。実際、この 2 つの境界は、自分自身の理解や規範によって完全に決定できます。

takeIf 関数と takeUnless 関数

takeif関数:

public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    
    
    contract {
    
    
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
}

takeUnless関数

public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    
    
    contract {
    
    
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
}

takeIf 関数: コンテキスト オブジェクトはラムダ式のパラメータです。ラムダ式はブール値を返します。true の場合はコンテキスト オブジェクト自体を返し、それ以外の場合は null を返します。つまり、コンテキスト オブジェクトに対してロジックが実行されます。ロジックが true の場合、コンテキスト オブジェクトが返されます。ロジックが false の場合、null が返されます。takeUnless 関数は、takeIf 関数とは逆のロジックを持ちます。アプリケーションのシナリオを考える必要はありません。データは条件に従ってフィルタリングされます。

公式の例:

val number = Random.nextInt(100)

val evenOrNull = number.takeIf {
    
     it % 2 == 0 }
val oddOrNull = number.takeUnless {
    
     it % 2 == 0 }

takeIf と takeUnless の後に他の関数への呼び出しを連鎖するときは、戻り値が null 許容であるため、null チェックまたは安全な呼び出し (?.) を実行することを忘れないでください。

おすすめ

転載: blog.csdn.net/weixin_43864176/article/details/128483725