いつrun、let、apply、およびwithをKotlinで使用する必要があるかの例


回答:


121

これらの関数はすべて、現在の関数/変数のスコープを切り替えるために使用されます。それらは、1つの場所に一緒に属するものを保持するために使用されます(主に初期化)。

ここではいくつかの例を示します。

run -必要なものをすべて返し、使用されている変数のスコープを変更します this

val password: Password = PasswordGenerator().run {
       seed = "someString"
       hash = {s -> someHash(s)}
       hashRepetitions = 1000

       generate()
   }

パスワードジェネレータは、現在としてrescopedされthis、私たちはそのために設定することができseedhashおよびhashRepetitions変数を使用しません。 generate()のインスタンスを返しますPassword

apply似ていますが、戻りthisます:

val generator = PasswordGenerator().apply {
       seed = "someString"
       hash = {s -> someHash(s)}
       hashRepetitions = 1000
   }
val pasword = generator.generate()

これは、Builderパターンの代わりとして、および特定の構成を再利用する場合に特に役立ちます。

let-主にnullチェックを回避するために使用されますが、の代わりに使用することもできrunます。違いは、それthisは以前と同じであり、次を使用して再スコープされた変数にアクセスすることですit

val fruitBasket = ...

apple?.let {
  println("adding a ${it.color} apple!")
  fruitBasket.add(it)
}

上記のコードは、リンゴがnullでない場合にのみ、リンゴをバスケットに追加します。また、これitオプションではなくなったため、ここでNullPointerExceptionが発生することはありません(別名?.、属性にアクセスするためにを使用する必要はありません)。

also-使いapplyたいがシャドウしたくないときに使うthis

class FruitBasket {
    private var weight = 0

    fun addFrom(appleTree: AppleTree) {
        val apple = appleTree.pick().also { apple ->
            this.weight += apple.weight
            add(apple)
        }
        ...
    }
    ...
    fun add(fruit: Fruit) = ...
}

applyここで使用するとシャドウthisが発生するため、フルーツバスケットではなくthis.weight、リンゴを参照します。


注:私は恥知らずに私のブログから取りました


2
私のような最初のコードに驚いた人にとって、ラムダの最後の行はKotlinの戻り値と見なされます。
ジェイ・リー

62

ここのような記事が他にもいくつかあり、ここで一見の価値があります。

数行以内でより短く、より簡潔にする必要があり、分岐や条件付きステートメントのチェックを回避する必要がある場合(nullでない場合など)にかかっていると思います。

このシンプルなチャートが大好きなので、ここにリンクしました。あなたはからそれを見ることができ、このセバスGottardoによって書かれたよう。

ここに画像の説明を入力してください

以下の私の説明に付随するチャートもご覧ください。

概念

これらの関数を呼び出すときのコードブロック内でのロールプレイングの方法と思います+自分自身を元に戻したいかどうか(関数の呼び出しをチェーンするか、結果変数に設定するかなど)。

上記は私が思うことです。

コンセプト例

ここでそれらすべての例を見てみましょう

1.)myComputer.apply { }は、メインアクターとして行動したい(自分がコンピューターだと思いたい)こと、そして自分自身を元に戻したい(コンピューター)ことを意味します。

var crashedComputer = myComputer.apply { 
    // you're the computer, you yourself install the apps
    // note: installFancyApps is one of methods of computer
    installFancyApps() 
}.crash()

うん、あなた自身はアプリをインストールし、クラッシュし、他の人がそれを見て何かをすることができるように参照として自分自身を保存しました。

2.)myComputer.also {}は、自分がコンピューターではないことを完全に確信していること、それを使って何かをしたい、そして返される結果としてコンピューターを望んでいる部外者であることを意味します。

var crashedComputer = myComputer.also { 
    // now your grandpa does something with it
    myGrandpa.installVirusOn(it) 
}.crash()

3.)with(myComputer) { }は、あなたが主役(コンピューター)であり、結果として自分自身を取り戻したくないことを意味します。

with(myComputer) {
    // you're the computer, you yourself install the apps
    installFancyApps()
}

4.)myComputer.run { }は、あなたが主役(コンピューター)であり、結果として自分自身を取り戻したくないことを意味します。

myComputer.run {
    // you're the computer, you yourself install the apps
    installFancyApps()
}

しかし、次のようにwith { }チェーンコールできるという非常に微妙な意味とは異なりrun { }ます

myComputer.run {
    installFancyApps()
}.run {
    // computer object isn't passed through here. So you cannot call installFancyApps() here again.
    println("woop!")
}

これはrun {}拡張機能によるものですが、そうでwith { }はありません。したがって、呼び出すrun { }this、コードブロック内が呼び出し元タイプのオブジェクトに反映されます。あなたは見ることができ、これをとの違いのために優れた説明をrun {}してwith {}

5.)myComputer.let { }は、あなたがコンピュータを見る部外者であり、コンピュータインスタンスが再びあなたに返されることを気にせずにそれについて何かをしたいということを意味します。

myComputer.let {
    myGrandpa.installVirusOn(it)
}

それを見る方法

私が見てする傾向があるalsolet外部、外にあるものとして。あなたがこれらの2つの言葉を言うときはいつでも、それはあなたが何かに行動しようとするようなものです。letこのコンピュータにウイルスをインストールし、alsoクラッシュさせます。だから、これはあなたが俳優であるかどうかの部分を釘付けにします。

結果の部分については、明らかにそこにあります。alsoそれは別のことでもあることを表現しているので、オブジェクト自体の可用性を維持します。したがって、結果としてそれを返します。

他のすべてはに関連付けられていthisます。さらに、run/with明らかにオブジェクトを返すことに関心がありません-セルフバック。今、あなたはそれらすべてを区別することができます。

100%プログラミング/ロジックベースの例から離れると、物事を概念化するのに適した立場になることがあると思います。しかし、それは正しいかによります:)


1
この図はそれをすべて示しています。これまでで最高。
ShukantPal19年

これは受け入れられ、最も投票された回答であるはずです
Segun Wahaab

8

また、apply、takeIf、takeUnlessはKotlinの拡張関数です。

これらの関数を理解するには、Kotlinの拡張関数Lambda関数を理解する必要があります。

拡張機能:

拡張関数を使用することで、クラスを継承せずにクラスの関数を作成できます。

Kotlinは、C#やGosuと同様に、クラスから継承したり、Decoratorなどの任意のタイプのデザインパターンを使用したりすることなく、新しい機能でクラスを拡張する機能を提供します。これは、拡張機能と呼ばれる特別な宣言を介して行われます。Kotlinは拡張機能と拡張プロパティをサポートしています。

したがって、の数値のみを見つけるにはStringStringクラスを継承せずに次のようなメソッドを作成できます。

fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())

上記の拡張機能は次のように使用できます。

val phoneNumber = "8899665544"
println(phoneNumber.isNumber)

これはプリントtrueです。

ラムダ関数:

Lambda関数は、JavaのInterfaceと同じです。ただし、Kotlinでは、ラムダ関数を関数のパラメーターとして渡すことができます。

例:

fun String.isNumber(block: () -> Unit): Boolean {
    return if (this.matches("[0-9]+".toRegex())) {
        block()
        true
    } else false
}

ご覧のとおり、ブロックはラムダ関数であり、パラメーターとして渡されます。上記の関数は次のように使用できます。

val phoneNumber = "8899665544"
    println(phoneNumber.isNumber {
        println("Block executed")
    })

上記の関数は次のように出力されます、

Block executed
true

これで、拡張関数とラムダ関数についてのアイデアが得られたと思います。これで、拡張機能に1つずつ移動できます。

しましょう

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

上記の機能で使用される2つのタイプTおよびR。

T.let

TStringクラスのような任意のオブジェクトにすることができます。したがって、この関数は任意のオブジェクトで呼び出すことができます。

block: (T) -> R

letのパラメーターには、上記のラムダ関数が表示されます。また、呼び出し元のオブジェクトは関数のパラメーターとして渡されます。したがって、関数内で呼び出し元のクラスオブジェクトを使用できます。次に、R(別のオブジェクト)を返します。

例:

val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }

上記の例では、ラムダ関数のパラメーターとしてStringを受け取り、Pairを返します。

同様に、他の拡張機能も動作します。

また

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

拡張関数alsoは、呼び出し元のクラスをラムダ関数パラメーターとして受け取り、何も返しません。

例:

val phoneNumber = "8899665544"
phoneNumber.also { number ->
    println(number.contains("8"))
    println(number.length)
 }

適用する

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

また同じですが、関数として渡される同じ呼び出しオブジェクトなので、関数やパラメーター名を呼び出さなくても関数やその他のプロパティを使用できます。

例:

val phoneNumber = "8899665544"
phoneNumber.apply { 
    println(contains("8"))
    println(length)
 }

上記の例では、ラムダ関数内で直接呼び出されるStringクラスの関数を確認できます。

takeIf

public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null

例:

val phoneNumber = "8899665544"
val number = phoneNumber.takeIf { it.matches("[0-9]+".toRegex()) }

上記の例でnumberは、phoneNumberに一致する文字列のみが含まれregexます。それ以外の場合はになりますnull

takeUnless

public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null

takeIfの逆です。

例:

val phoneNumber = "8899665544"
val number = phoneNumber.takeUnless { it.matches("[0-9]+".toRegex()) }

numberphoneNumber一致しない場合にのみ、の文字列が含まれregexます。それ以外の場合はになりますnull

Kotlinの違いも、apply、let、use、takeIf、takeUnlessで、ここで役立つ同様の回答を表示できます。


最後の例ではタイプミスがあります。おそらく、のphoneNumber. takeUnless{}代わりに意味しますphoneNumber. takeIf{}
ライアンアマラル2018

1
修正しました。ありがとう@RyanAmaral
Bhuvanesh BS

5

6つの異なるスコープ関数があります。

  1. T.run
  2. T.let
  3. T.apply
  4. T.also
  5. 実行

違いを示すために、以下のような視覚的なメモを用意しました。

data class Citizen(var name: String, var age: Int, var residence: String)

ここに画像の説明を入力してください

決定はあなたのニーズに依存します。異なる関数のユースケースは重複しているため、プロジェクトまたはチームで使用されている特定の規則に基づいて関数を選択できます。

スコープ関数はコードをより簡潔にする方法ですが、使いすぎないようにしてください。コードの可読性が低下し、エラーが発生する可能性があります。スコープ関数をネストすることは避け、それらをチェーンするときは注意してください。現在のコンテキストオブジェクトとこれまたはその値について混乱するのは簡単です。

https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84からどちらを使用するかを決定するための別の図を次に示します。 ここに画像の説明を入力してください

いくつかの規則は次のとおりです。

デバッグ情報のログ記録や印刷など、オブジェクトを変更しない追加のアクションに使用します。

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

適用の一般的なケースは、オブジェクト構成です。

val adam = Person("Adam").apply {
age = 32
city = "London"        
}
println(adam)

シャドウイングが必要な場合は、runを使用します

fun test() {
    var mood = "I am sad"

    run {
        val mood = "I am happy"
        println(mood) // I am happy
    }
    println(mood)  // I am sad
}

あなたはレシーバオブジェクト自体を返すために必要がある場合は、使用が適用されますまた、


3

私の経験によると、このような関数はパフォーマンスの違いがないインラインシンタックスシュガーであるため、ラムダでのコードの記述が最も少ない関数を常に選択する必要があります。

これを行うには、最初にラムダに結果を返すか(choose run/ let)、オブジェクト自体を返すか(choose apply/ also)を決定します。ラムダが単一の式である場合、ほとんどの場合、その式と同じブロック関数タイプを持つものを選択します。これは、レシーバー式の場合thisは省略でき、パラメーター式の場合itは次の式よりも短いためthisです。

val a: Type = ...

fun Type.receiverFunction(...): ReturnType { ... }
a.run/*apply*/ { receiverFunction(...) } // shorter because "this" can be omitted
a.let/*also*/ { it.receiverFunction(...) } // longer

fun parameterFunction(parameter: Type, ...): ReturnType { ... }
a.run/*apply*/ { parameterFunction(this, ...) } // longer
a.let/*also*/ { parameterFunction(it, ...) } // shorter because "it" is shorter than "this"

ただし、ラムダがそれらの組み合わせで構成されている場合は、コンテキストにより適したものを選択するか、より快適に感じるかはあなた次第です。

また、分解が必要な場合は、パラメータブロック機能を備えたものを使用してください。

val pair: Pair<TypeA, TypeB> = ...

pair.run/*apply*/ {
    val (first, second) = this
    ...
} // longer
pair.let/*also*/ { (first, second) -> ... } // shorter

これは、Java開発者向けのCourseraKotlinに関するJetBrainsの公式Kotlinコースのこれらすべての関数の簡単な比較です。 差異表 簡素化された実装

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.