各関数の実行の良い例が欲しいのですが、let、apply、また、
回答:
これらの関数はすべて、現在の関数/変数のスコープを切り替えるために使用されます。それらは、1つの場所に一緒に属するものを保持するために使用されます(主に初期化)。
ここではいくつかの例を示します。
run
-必要なものをすべて返し、使用されている変数のスコープを変更します this
val password: Password = PasswordGenerator().run {
seed = "someString"
hash = {s -> someHash(s)}
hashRepetitions = 1000
generate()
}
パスワードジェネレータは、現在としてrescopedされthis
、私たちはそのために設定することができseed
、hash
および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
、リンゴを参照します。
ここのような記事が他にもいくつかあり、ここで一見の価値があります。
数行以内でより短く、より簡潔にする必要があり、分岐や条件付きステートメントのチェックを回避する必要がある場合(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)
}
私が見てする傾向があるalso
とlet
外部、外にあるものとして。あなたがこれらの2つの言葉を言うときはいつでも、それはあなたが何かに行動しようとするようなものです。let
このコンピュータにウイルスをインストールし、also
クラッシュさせます。だから、これはあなたが俳優であるかどうかの部分を釘付けにします。
結果の部分については、明らかにそこにあります。also
それは別のことでもあることを表現しているので、オブジェクト自体の可用性を維持します。したがって、結果としてそれを返します。
他のすべてはに関連付けられていthis
ます。さらに、run/with
明らかにオブジェクトを返すことに関心がありません-セルフバック。今、あなたはそれらすべてを区別することができます。
100%プログラミング/ロジックベースの例から離れると、物事を概念化するのに適した立場になることがあると思います。しかし、それは正しいかによります:)
また、apply、takeIf、takeUnlessはKotlinの拡張関数です。
これらの関数を理解するには、Kotlinの拡張関数とLambda関数を理解する必要があります。
拡張機能:
拡張関数を使用することで、クラスを継承せずにクラスの関数を作成できます。
Kotlinは、C#やGosuと同様に、クラスから継承したり、Decoratorなどの任意のタイプのデザインパターンを使用したりすることなく、新しい機能でクラスを拡張する機能を提供します。これは、拡張機能と呼ばれる特別な宣言を介して行われます。Kotlinは拡張機能と拡張プロパティをサポートしています。
したがって、の数値のみを見つけるにはString
、String
クラスを継承せずに次のようなメソッドを作成できます。
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
T
Stringクラスのような任意のオブジェクトにすることができます。したがって、この関数は任意のオブジェクトで呼び出すことができます。
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()) }
number
とphoneNumber
一致しない場合にのみ、の文字列が含まれregex
ます。それ以外の場合はになりますnull
。
Kotlinの違いも、apply、let、use、takeIf、takeUnlessで、ここで役立つ同様の回答を表示できます。
phoneNumber. takeUnless{}
代わりに意味しますphoneNumber. takeIf{}
。
6つの異なるスコープ関数があります。
違いを示すために、以下のような視覚的なメモを用意しました。
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
}
あなたはレシーバオブジェクト自体を返すために必要がある場合は、使用が適用されますかまた、
私の経験によると、このような関数はパフォーマンスの違いがないインラインシンタックスシュガーであるため、ラムダでのコードの記述が最も少ない関数を常に選択する必要があります。
これを行うには、最初にラムダに結果を返すか(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コースのこれらすべての関数の簡単な比較です。