blog

[翻訳】あなたは殴られたことがあるか? Kotlinの隠された問題

序文\n原題:Kotlinのlet and runの話\nKotlinのパフォーマンス低下やKotlinの小技に関する記事をいくつか公開していますので、未読の方は下記からどうぞ。...

Apr 29, 2020 · 15 min. read
シェア

序文

  • : A decompiled story of Kotlin let and run

この記事は主にKotlinのもう一つの隠された問題を分析することです、記事は2つの部分、 翻訳と 翻訳者の思考に 分割されます、この記事は単なる翻訳ではありません、翻訳の 翻訳者の思考の 部分では、要約されるだけでなく、より深い思考と分析、また、翻訳 者の思考の 部分を読むために直接翻訳の翻訳をスキップすることができます。

この記事を通して、翻訳者の反省のセクションで答えられる以下のことを学ぶことができます。

  • T.letを使うことの問題点は何ですか?
  • なぜこのような問題が起きたのでしょうか?
  • どうすれば解決できますか?
  • なぜT.applyを使うとこの問題が解決するのですか?
  • T.applyとT.letの違いは何ですか?
  • run、with、let、also、applyの使い分けは?
  • Kotlinはどのように2つの変数を交換するのですか?

この記事は多くの重要なポイントをカバーしていますので、辛抱強く読み進めてください。

翻訳

letとrunはKotlin標準ライブラリのインライン関数で、古典的なif ... else ... ステートメントと同じように機能すると思うので、私はプロジェクトである機能を実装するために隠れた問題にぶつかるまで、そのように使ってきました。

doSomeAwesomePrinting() これは非常に単純なKotlinのコードで、2つのnull可能な変数があり、そのうちの1つはすでに値を持っています。

私のように何も出力されないと思うかもしれませんが...。 それは間違いです。"awesome output 1 "と出力されてしまいます。

なぜこのようなことが起こるのでしょうか?デコンパイルされたコードとその結果を見てみましょう。

ご覧の通り、2つ目の変数 awesomeVar2 が null の場合、Kotlin が自動生成する変数 var00100 も null なので、プログラムは return 文を実行せず、関数の実行終了時に「awesome output 1」と出力します。

別の例を見てみましょう。上のKotlinコードに変更を加え、2番目の変数にelvis演算子を追加します。

doSomeAwesomePrinting() このメソッドが再度呼び出されると、"awesome output 3 "と出力されます。 文とは対照的です。 デコンパイルしたコードを見てください。

ご覧のように、デコンパイルされたコードは実際には if ... else ... 文であり、2番目の変数がNULLの時に "awesome output 3 "を出力しています。 では、記事の冒頭で提起された問題を解決する方法を分析してみましょう。

処方

ダニーの提案のおかげで、Kotlinの他のインライン関数であるapplyを使えば、この問題を解決できます。

次に、ダニーの提案に従って別のKotlinインライン関数を使い、適用して、何が起こるか見てみましょう。

予想通り、2番目の変数がNULLの場合、コンソールは何も出力しません。

ご覧のように、Kotlinはnullでない変数var1を自動的に生成し、2番目の変数awesomeVar2がnullの場合は、それを返すだけです。

最後に、上記のKotlinコードをapplyから少し修正し、2番目の変数にelvis演算子を追加すると以下のようになります。

コードを実行すると、2番目の変数がnullのとき、コンソールに "awesome output 3 "と出力されます。 ここで何が起こっているのかを理解するために、逆コンパイルされたコードを見てみましょう。

生成されたコードはそれ以上のものですが、通常の分析には影響しませんし、予想通りコンソールには "awesome output 3" と出力されます。

翻訳者の考察

次のセクションは、翻訳者の考察のセクションで、このスタイルでは、翻訳の概要から始まり、分析が続きます。

概要と分析

T.letを使うことの問題点は何ですか?

Kotlinの標準ライブラリには、T.letやT.runといったインライン関数があり、if ... と同じように動作します。 else ... ステートメントと同じように動作します。 else ... ステートメントなので、次のコードを実行することはif ... else ... ステートメントと同じだと思うかもしれません。 else ... 文は何も出力しません。

class ExampleClass {
 var awesomeVar1: String? = "some awesome string value"
 var awesomeVar2: String? = null
 fun doSomeAwesomePrinting() {
 awesomeVar1?.let {
 awesomeVar2?.let {
 println("awesome output 2")
 }
 } ?: run {
 println("awesome output 1")
 }
 }
}

しかし、結果は驚くべきもので、2番目の変数が空の場合、実際には "すごい出力1 "を出力し、あなたが見るために上記のコードを逆コンパイルすることができ、その内部ロジックについて、より明確になります。

なぜこのような問題が起きたのでしょうか?

Kotlinのインライン関数T.letのソース実装を見てみましょう。

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

ご覧のように、T.letはパラメータitを受け取り、最後の行を返します。次にソースコードを逆アセンブルすると、何が起こっているのかがはっきりするかもしれません。

fun doSomeAwesomePrinting() {
 awesomeVar1?.let {
 // 将 awesomeVar2?.let{...} の結果である。 awesomeVar1?.let{...} の戻り値である。
 // つまり、awesomeVar2が空の場合、awesomeVar2は空になる。?.let{...} の結果である。
 // この関数は最終的に "awesome output 1"
 awesomeVar2?.let { println("awesome output 2") }
 } ?: run {
 println("awesome output 1")
 }
}
  • awesomeVar2?.let{...} awesomeVar1?.let{...} 結果を
  • awesomeVar2?.let{...} awesomeVar2がNULLの場合、結果はNULLです。
  • 関数は "awesome output 1" で終了します。

どうすれば解決できますか?

解決策は、以下のコードに示すように、Kotlinの別のインライン関数であるT.applyを使用することも非常に簡単です。

class ExampleClass {
 var awesomeVar1: String? = "some awesome string value"
 var awesomeVar2: String? = null
 fun doSomeAwesomePrinting() {
 awesomeVar1?.apply {
 awesomeVar2?.apply {
 println("awesome output 2")
 }
 } ?: run {
 println("awesome output 1")
 }
 }
}

別のKotlinのインライン関数であるT.applyを使うと、期待通りに何も出力されません。では、なぜT.applyを使うとこの問題が解決するのか、T.applyとT.letの違いは何なのでしょうか?

なぜT.applyを使うとこの問題が解決するのですか?

Kotlinのインライン関数T.applyのソース実装を見てみましょう。

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

T.apply関数は独自の戻り値を持つ拡張関数で、thisというパラメータを受け取ります。

fun doSomeAwesomePrinting() {
 awesomeVar1?.apply {
 // awesomeVar1?.apply{...} awesomeVar1の戻り値はそれ自身であり、awesomeVar1はNULLではない。
 // つまり、awesomeVar2が空の場合は何も出力されない。
 awesomeVar2?.apply { println("awesome output 2") } // awesomeVar2?.apply{...} 戻り値は awesomeVar2
 } ?: run {
 println("awesome output 1")
 }
}
  • awesomeVar1?.apply{...} T.apply関数の性質上、awesomeVar1の戻り値はそれ自身であり、変数awesomeVar1はNULLではありません。
  • awesomeVar1?.apply{...} awesomeVar2 が空の場合、, の結果には影響しないので、ここでは何も出力されません。

T.applyとT.letの違いは何ですか?

T.letで御座いますit最終行
T.applyで御座いますthis呼び出す

KotlinにはT.applyやT.letの他にも、T.run、T.also、withなど多くのインライン関数があります。 演算子の数はそれほど多くないのですが、見分けるのが難しいので、簡単な見分け方と使い方を紹介します。

run、with、let、also、applyの見分け方

この記事を書いてくれたElyeに感謝します

run、with、let、also、applyの区別については、拙稿「 Kotlinの技法と原則で知っている人は少ない 」で取り上げているので、ここでおさらいしておきます。

run、with、let、also、applyはKotlinのインライン関数で、スコープ付き関数でもあります。 これらのスコープ付き関数がどのように使われ、どのように区別されるかは、以下の3つの次元で区別されます。

  • 拡張機能かどうか。
  • スコープ関数の引数。
  • スコープされた関数の戻り値。

拡張機能かどうか

まず、withとT.runを見てみましょう。この2つの関数はよく似ていますが、withは通常の関数で、T.runは拡張関数であるという違いがあります。

val name: String? = null
with(name){
 val subName = name!!.substring(1,2)
}
// その無効性をチェックすることができる。
name?.run { val subName = name.substring(1,2) }?:throw IllegalArgumentException("name must not be null")

この場合、name?.runの方が、使用時にnullかどうかをチェックできるので良いでしょう。

スコープ付き関数への引数

T.runとT.letを見ると、どちらも拡張関数ですが、引数が異なります。T.runは引数として thisを取り、T.letは引数として thisを取ります。

val name: String? = "hi-dhl.com"
// パラメータはthisで、省略可能である。
name?.run {
 println("The length is ${this.length} this 省略することもできる。 ${length}")
}
//   it
name?.let {
 println("The length is ${it.length}")
}
// カスタム・パラメータ名
name?.let { str ->
 println("The length is ${str.length}")
}

上記の例では、thisを省略することができ、呼び出しがより簡潔なので、T.runの方が良いように思えますが、T.letではパラメータ名をカスタムすることができ、より読みやすくなるので、読みやすさを重視するのであれば、T.letを選択することができます。

スコープされた関数の戻り値

T.letは最後の行を返し、T.alsoは呼び出し元自体を返します。

var name = "hi-dhl"
// 呼び出しそのものを返す
name = name.also {
 val result = 1 * 1
 ""
}
println("name = ${name}") // name = hi-dhl
// 最終行が返された
name = name.let {
 val result = 1 * 1
 "hi-dhl.com"
}
println("name = ${name}") // name = hi-dhl.com

上記の例から、T.alsoは意味がないように見えるが、それは実際には非常に意味のあることを考えて、自己操作の使用では、他の機能と組み合わせることで、機能がより強力になります。

fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }

もちろん、T.も自己操作特性の使用でT.も使用するなど、他のことを行うことができます、あなたはコードの行を達成することができます2つの変数を交換するには、後者の詳細に説明されます。

T.apply

この関数は独自の戻り値を持つ拡張関数で、パラメータ thisを受け取ります。

// 普通の方法
fun createInstance(args: Bundle) : MyFragment {
 val fragment = MyFragment()
 fragment.arguments = args
 return fragment
}
// 改善された方法論
fun createInstance(args: Bundle) 
 = MyFragment().apply { arguments = args }
 
 
// 普通の方法
fun createIntent(intentData: String, intentAction: String): Intent {
 val intent = Intent()
 intent.action = intentAction
 intent.data=Uri.parse(intentData)
 return intent
}
// 改善された方法論、連鎖するコール
fun createIntent(intentData: String, intentAction: String) =
 Intent().apply { action = intentAction }
 .apply { data = Uri.parse(intentData) }

T.also関数を使用して2つの変数を入れ替えます。

次のデモは、T.also 関数を使って、2 つの変数を入れ替えるコード行を実装するものです。まず、Javaが何をするのかを復習しましょう。

int a = 1;
int b = 2;
// Java - 中間変数
int temp = a;
a = b;
b = temp;
System.out.println("a = "+a +" b = "+b); // a = 2 b = 1
// Java - 加減算
a = a + b;
b = a - b;
a = a - b;
System.out.println("a = " + a + " b = " + b); // a = 2 b = 1
 
// Java -  
a = a ^ b;
b = a ^ b;
a = a ^ b;
System.out.println("a = " + a + " b = " + b); // a = 2 b = 1
// Kotlin
a = b.also { b = a }
println("a = ${a} b = ${b}") // a = 2 b = 1

ここでは、T.alsoの2つの機能が使われています。

  • T.also関数の呼び出しは、呼び出し元自身を返します。
  • 使用中はセルフオペレーションが可能です。

つまり、b.also { b = a }は、aの値をbに代入して1にしてから、bの元の値をaに代入して2にして、2つの変数を入れ替えます。

アグリゲイション

より簡単に理解し、覚えておくために、次の私は、上記の内容を要約するために表の形になりますが、具体的なまた、実際のプロジェクトを組み合わせて使用する必要があります。

withフォールトthis最終行
T.runで御座いますthis最終行
T.letで御座いますit最終行
T.alsoで御座いますit呼び出す
T.applyで御座いますthis呼び出す

フルテキストは、ここで終わりに、あなたが使用するプロジェクトで柔軟に対応することができ、あなたがKotlinでそれらの落とし穴に遭遇した場合は、コードをより読みやすくすることができ、コメント欄で共有することを歓迎し、より優れた英語の技術記事は、ここをクリック PokemonGo 倉庫の住所-/Go

AndroidX-Jetpack-Practice

結語

Androidシステムのソースコード、逆解析、アルゴリズム、翻訳、Jetpackのソースコード関連の記事のシリーズを共有することにコミットし、より良い記事を書くためにしようとしている、この記事はあなたが星を与えるために役立つ場合は、場所を理解するために記事に書かれていないもの、またはどのようなより良いアドバイスを歓迎メッセージを残して、一緒に前進する技術の道で、一緒に学ぶことを歓迎します。

は、戦闘プロジェクトや関連コンポーネントの原理分析の記事の中で最も完全かつ最新のAndroidX Jetpack関連コンポーネントを構築する過程で、現在すでにアプリの起動、Paging3、ヒルトなどが含まれ、徐々に他のJetpackの新しいメンバーを追加している、倉庫は更新し続け、あなたがチェックするために行くことができます:AndroidX-。Jetpackの練習は、このリポジトリがあなたに有用である場合、私はのようなポイントを助けるために倉庫の右隅にしてください。

算術

LeetCodeには各カテゴリーに数百問の膨大なデータベースがあり、各人のエネルギーには限りがあるため、すべての問題をブラッシュアップすることは不可能なので、問題の古典的なタイプ、問題の難易度によって分類しました。

  • データ構造:配列、スタック、キュー、文字列、チェーン・リスト、ツリー..........
  • アルゴリズム:検索アルゴリズム、検索アルゴリズム、ビット演算、ソート、数学、..........

それぞれのトピックはJavaとkotlinで実装され、それぞれのトピックには解、時間の複雑さ、空間の複雑さがあります。もし私と同じようにアルゴリズムとLeetCodeが好きなら、私のGitHubのLeetCodeの問題解決:Leetcode-Solutions-with-Java-And-Kotlinをフォローしてください。もし私と同じようにLeetCodeが好きなら、私のGitHub LeetCodeの問題解決:Leetcode-Solutions-with-Java-And-Kotlinをフォローして一緒に学びましょう。

Read next

cache2goソースコードの解釈

cache2goは、オープンソースのアプリケーション内キャッシュライブラリです。 キーと値は任意のデータ型にすることができます。ソースコードでは、ライブラリがデータを維持する方法、同時実行のセキュリティを確保するためにロックを使用する方法、データ処理の有効期限を設定する方法を学ぶことができます。 Cache():既に存在するデータを返します。

Apr 29, 2020 · 8 min read