Kotlin、パズル、2 Kekses:Kotlinの動作を知っていますか?

最初はJavaがあり(まあ、最初からではありませんが、私たちの話はここから始まります)、時間が経ち、20年後、JetBrainsの賢い人たちがKotlinを設計し、立ち上げました。強力で透明。

かつて、Andrei abreslav Breslavは、Kotlinは便利で予測可能な言語として開発されたと述べました。 同時に、この言語では困惑者(短いコード、結果は予想外、恐ろしい、または失望する)は見つからないとの意見がありました。 Anton antonkeks KeksはIDEAで召喚し、まだ何かを掘り起こしました。そして、実例でさえ、彼はPhilip Keksと協力して彼の発見について話しました。 自分で見てください:



カットの下-そのようなパズルの選択とそれらに関する詳細なコメント。 この資料は、 Mobius 2017カンファレンス(サンクトペテルブルク)での Anton Keks(Codeborne)とPhilip Keks(Creative mobile)のレポートに基づいています。

Kotlinから始めましょう。 誰もがジャワには多くの問題があると言っています。島にはたくさんの火山があり、地震があります。 彼女は救われる必要があります。


そのため、コトリンという別の島が思い浮かびます。


落ち着いて、何も起こりません。 それは非常に平らで、火山はありません。 近くにあります。 したがって、Kotlinは、特に私たちのようなAndroid開発者にとって、Javaの救世主です。

コトリンについての一言


コトリンとは、多かれ少なかれ誰もが知っていることです。 Kotlinなしで今日Android向けに書いているのはどのような愚か者ですか? これは、私にはすでにマゾヒズムのようです。 それは素晴らしく機能します。 数週間前、最初のKotlinネイティブビルドがリリースされました。 まもなく、KotlinでiOSを使用して作成する予定です。

これは実用的な言語、オープンソース、非常にクールなチューニングです-IDEがうまく機能するように設計されています。 これは、AppleのSwift言語などの庭にある石です。 JetBrainsは優れたプッシュ型Kotlinです。特にIDE用の言語を設計しています。

Kotlinの開発には非常に長い時間がかかったことは誰もが知っています。 バージョン1.0がリリースされるまでに6年が経過しました。 JetBrainsは一生懸命努力しましたが、明らかに新しい言語を作成することはそれほど簡単ではありません。 過去数年間(2010年から2016年)、彼らはロゴをより現代的なものに変更することさえできました。


どれくらいの期間開発されているかを考えると、この言語は優れているはずです。 他の多くの言語がはるかに高速に開発されているため、これは世界最高の言語である必要があります。 たとえば、誰もがJavaScriptが2週間で完了したことを知っています。 もちろん、これはロケット科学ではありませんが(ロケット科学はSpaceXであり、4年で実際のロケットのプラットフォームに着陸することを学びました)。

そして最も重要なことは、Kotlinがサンクトペテルブルクで開発されており、これがロシアの数少ないプログラミング言語の1つであるためです。 古いロシアのプログラミング言語は次のようになりました(右側):


幸いなことに、この言語は国際的な視聴者を対象としていたため、「fu」キーワードの代わりに、作成者は楽しいキーワードを使用することを決定しました。 これは楽しい言語です。

ジグソーパズル


パズルゲームとは何ですか?

これらは興味深い動作を備えたKotlinで書かれた短いプログラムです。 そして、あなたは彼らが印刷するものを推測します。 提案された回答オプションに投票すると、最初に手を挙げて正しいオプションを推測するだけでなく、その理由を説明した人が賞品を受け取ります。

パズルの前半は、Kotlinにあまり詳しくない人を対象としています。 後半は筋金入りのKotlin開発者向けです。

Kotlinは、有名なJavaパズルゲームのいくつかを繰り返さないことで知られています。 ただし、理想的なプログラミング言語では、パズルはまったくありません。 Kotlinは完全ではないことがわかりました-完全な言語はありません。

しかし、Kotlinはすでに多くのモバイルアプリケーションで採用されています。 この言語は、多くの場合、実用的で便利なものとして作成されました。 使いやすさを含めて便利です。 そして彼はまだ開発中です。 Kotlinの開発者と話をして、どのように、何を修正するかについて同意することができます。

実証済みのすべてのパズルは、Kotlin 1.1.1-最新の安定バージョンで起動します。 パズルのソースコードはGitHubにあります-https: //github.com/angryziber/kotlin-puzzlers/tree/mobiusで確認できます
新しいパズルゲームのアイデアを思いついた人は誰でも、プルリクエストを送信してください。 待っています。

パズラー1


Kotlinは、null可能性をサポートしているという点で優れています。正確には、nullセーフです。

package p1_nullean val s: String? = null if (s?.isEmpty()) println("true") 

nullable型とnonnullable型には違いがあります。 つまり、nullをどこかに割り当てる場合は、null許容型(質問付き)でなければなりません。 このアイデアはおそらくC#によって提案されましたが、彼はそれを完成させませんでした-プリミティブのみがそこで使用可能になります。 また、Kotlinでは、これはすべてのタイプですでに正常に行われています。 基本的に、この言語は、実行時に恐ろしいNullPointerExceptionが発生しないように設計されています。

この例では、Kotlinは優れたヌルセーフ演算子sを採用していますか?Groovyから、ゼロで何らかのメソッドを呼び出すことができ、実行時にアクションをすぐに取得できません。

可能なオプションのどれを取得するか見てみましょう:


始めます。 見ます。



コンパイルされません。

なんで?

Kotlinは型安全な言語であるため、式s?.IsEmpty()の結果はnullであるため、falseにキャストされません。

簡単に修正できます(KotlinがGroovyと同じように動作するように記述する必要があります)。

 if (s?.isEmpty() ?: false) println("true") 

あまりきれいではありませんが、今はきれいです。 Kotlinovskyトラッカーは、nullをfalseと解釈する提案をすでに提出していますが、独自のニュアンスがあります。 この機能を取得するかどうかはまだ不明です。 さまざまな意見がありますが、この言語を使用することの利便性の観点から、これは小さな問題です。

パズラー2


パズルは非常に似ています。同じnull可能な文字列変数があり、そのメソッドを呼び出そうとしています。

 package p2_nulleanExtended val x: String? = null print(x.isNullOrEmpty()) 

結果はどうなりますか?


起動中...
答えは本当です。



なんで? これはKotlin標準ライブラリの拡張関数であり、ヌル可能なCharSequenceに「ハング」します。 したがって、このような場合、正常に処理されます。



実際、Kotlinでは、nullでいくつかの関数を実行できます。 コンパイラーはこれを認識しており、ユーザーはそれを行うことができます。

疑問符を付けると、IDEAはそれがここでは必要ないことを教えてくれます。

 print(x?.isNullOrEmpty()) 

関数の名前が人名になっているのは良いことです(名前で結果を推測できます)。

2番目のオプション(疑問符付き)で実行すると、結果はnullになります。これは、かっこ内の式がnullにキャストされ、nullをサポートしているにもかかわらずこの関数が呼び出されないためです。 ちなみに、これは別のパズルゲームです。

パズラー3


 package p3_platformNulls class Kotlin { fun hello(name: String) = print("Hello $name") } fun main(args: Array<String>) { val prop = System.getProperty("key") Kotlin().hello(prop) } 

Kotlinには、このような興味深い機能があります-ヌル可能性の3番目の状態。 を呼び出すとどうなるか見てみましょう。

 val prop = System.getProperty("key") 

そして、これをKotlinクラスのhelloメソッドに渡します。これにより、印刷されるはずです。

 Kotlin().hello(prop) 

出力は何になりますか?


一般に、型推論は素晴らしいトピックです。

始めます。 IllegalStateExeptionを受け取ります。



なんで?

prop値はnull、タイプはString!です。helloになり、実行時にnullであってはならないが、nullであることがチェックされます。

実際、Kotlinの初期バージョンでは、StringがJavaから来る場合、デフォルトでは常にnull可能です。

 val prop: String? = System.getProperty("key") 


これは、Javaと相互運用するときにコードを書くのが非常に不便になるという事実につながりました。 そして、仮想型の文字列を作成することにしました! (感嘆符付き)。 これはnullabilityの3番目のオプションであり、「わからない」と呼ばれます。

 val prop: String! = System.getProperty("key") 

ただし、タイプは文字列であるため、このようなコードはコンパイルされません! 自分で宣言することはできません。 Javaからのみ取得できます。

したがって、そのようなことを事前にnull可能またはnull不可として宣言することをお勧めします(原則として、APIからは、nullがいつかそこに来るかどうかを知っています)。

ただし、このようなコードはコンパイルされますが、実行時にクラッシュする可能性があります。

 val prop: String = System.getProperty("key") 

IDEAは、どのタイプがどこにあるかを常に知っています。 変数Ctrl + qをクリックして確認できます。
しかし、nullabilityで終わり、別のトピックに移りましょう。

パズラー4


印刷する2つの関数があります。 それらを発表して起動します-すべてがシンプルでなければなりません:

 package p4_kotlinVsScala fun main1() = print("Hello") fun main2() = { print("Hello2") } main1() main2() 

出力は何になりますか?


始めます...こんにちは。



なんで?

Main1はユニットを返しますが、print( "Hello")を呼び出します。 そしてmain2は、実行されないラムダを返すだけです。

次のように修正できます。

 main2()() 

私の意見では、2番目は、main2から等号を削除することです。これは、全員を混乱させるだけだからです。

 fun main2() { print("Hello 2") } 

この例をKotlin対Scalaと呼ぶのはなぜですか? Scalaで書いた人は、このコードが何かを返す関数の絶対的に有効な宣言であることを知っています。

 fun main2() = { } 

Kotlinで作成する貧弱なScala開発者。 彼らはおそらく開始することなくラムダを常に返します。

パズラー5


数値のリストがあり、forEachメソッドを繰り返し処理します。 ForEachは、Groovyと同様に、lambdaパラメーターが宣言されていない場合、それを認識しています。 そして、それが2以下であることを確認し、印刷します。

 package p5_sneakyReturn fun main(args: Array<String>) { listOf(1, 2, 3).forEach { if (it > 2) return print(it) } print("ok") } 

結果はどうなりますか?


起動中...



12
なんてナンセンス?

Kotlinでは、関数から戻り値が返されます。 また、特定のラムダを終了するには、この関数内で、戻り後にラムダの名前を指定する必要があります。

 if (it > 2) return@forEach 

したがって、ラムダから抜け出すことができます。 Javaで記述し、ラムダから抜け出す必要がある場合、これはコードを修正するオプションです。

実際、Kotlinでの返品は、本来の方法で機能します。 以前にC#とJavaで記述していなかった場合、メイン関数から戻り値が返されるため、おそらく間違えられなかっただろう。 すべてが論理的です。 また、ラムダには奇妙な機能はありませんが、何らかの理由で抜け出す必要があります。

なぜこのように機能するのですか?



forEach関数は、インライン関数として宣言されます。 Kotlinでは、コンパイラはコンパイルされたコードでこの関数を呼び出しませんが、この関数のコードを取得し、呼び出しがあった場所に挿入します。 その結果、通常のforループを取得し、もちろん、メイン関数を終了します。

これがインライン関数であることを理解する方法は? まず、IDEAにはCtrl + pがあります。 次に、returnを呼び出し、関数がインラインでない場合、コンパイラーは「ごめん、これはできません」と言います。 つまり、コンパイラはナンセンスを行うことを許可しません。

12okを返すようにこのコードを修正する別のオプションがあります。 これは、ラムダではなく関数として宣言する必要があります。

 fun main(args: Array<String>) { listOf(1, 2, 3).forEach(fun() { if (it > 2) return print(it) }) print("ok") } 

匿名関数とラムダを使用したKotlinの唯一の違いは、最初の関数が関数とまったく同じように動作することです。つまり、returnは最も近い「fun」(fun)から戻ります。 したがって、この修正により、正常に機能します。

さらに興味深いものにするために、いくつかの例を用意しました。 Kotlinにはさまざまなキーワードがあります。


それらのいくつかは、returnを使用することを許可し、いくつかは許可しません。

 package p5_sneakyReturn fun hello(block: () -> Unit) = block() inline fun helloInline(block: () -> Unit) = block() inline fun helloNoInline(noinline block: () -> Unit) = hello(block) inline fun helloCrossInline(crossinline block: () -> Unit) = runnable { block() }.run() fun main(args: Array<String>) { hello { println("hello") //return - impossible } hello(fun() { println("hello") return }) helloInline { println("hello") return } helloNoInline { println("hello") //return - impossible } helloCrossInline { println("hello") //return - impossible } 

宿題、私たちはCrossinlineが何であるかを理解する欲求を残します。 面白いと思います。

Kotlinで書き始めたばかりのとき、私はそれが複雑なものだと思いました。 しかし、インライン関数が何であるかを理解すると(コレクションのほとんどすべての拡張関数はパフォーマンスのためにインラインになります)、すべてが非常に論理的になります。

パズラー6


ジョンまたはジャアンを取得する必要があります。

単純なPersonクラスがあります。 Kotlinでは非常に便利です。クラスを宣言するときに、コンストラクターをすぐに宣言できます。 コンストラクター変数名を取得し、それをプロパティに追加します。 Kotlinにはフィールドがありません。ゲッター、セッター、あらゆる種類のナンセンス(またはC#のようにgethとset)を記述する必要がないため、プロパティのみがあり、非常にクールです。 すばらしい美しい構文。

その結果、Johnという名前のPersonを作成し、それがJaanのエストニア語ローカリゼーションに変わるかどうかを確認します。

 package p6_getMeJohn class Person(name: String) { var name = name get() = if (name == "John") "Jaan" else name } println(Person("John").name) 


起動中...



これはスタックオーバーフローです。
なんで?
名前を取り、if-elseにし、getで呼び出します。 修正するには、プロパティではなくフィールドを参照する必要があります。 fieldキーワードを使用できます:

 class Person(name: String) { var name = name get() = if (field == "John") "Jaan" else field } 

Kotlinのフィールドキーワードでフィールドにアクセスできますが、これを行うことができる唯一の場所はゲッター/セッター内です。 他のすべての呼び出しは、プロパティを介してのみ行われます-フィールドに直接アクセスしません。

彼らは、.NET仮想マシンとは異なり、Java Hotspotコンパイラーがこれを最適化するため、パフォーマンスはすべてクールであり、すべてが非常に高速に動作すると言います。

パズラー7


繰り返しになりますが、言語の素晴らしい機能-型推論-を見て、どんな型のwhatAmIでもかまいません。とにかくそれを使用できます。 しかし、コンパイラはそれが何であるかを知っています。 知っているかどうか見てみましょう。

 package p7_whatAmI val whatAmI = {}() println(whatAmI) 

どのオプションが結果になりますか?


実行... kotlin.Unitを取得します。



なんで?

ここでラムダが宣言され、ラムダが呼び出されます。 lambdaは何も返さない(正確にはkotlin.Unitを返す)ため、これが出力されます。 そして、ユニットの最良の定義は無効です。

ユニットはどこから来たのですか? 私の意見では、数学(またはコンピューターサイエンス)でも、型理論のようなものがあります。 また、Unitは「何もない」という意味の1つの要素であると説明されています。 したがって、一部のアカデミックプログラミング言語では、ユニットという用語を使用します。 Kotlinは実用的な言語として設計されましたが、それにもかかわらず、その開発者は実用的なボイドを選択せず​​、Unitを作成することにしました。

さらにおもしろくするために、Kotlinにはkotlin.Nothingという別のタイプがあります。
それらはどう違うのですか? この質問に対する答えを宿題にします。

パズラー8


whatAmIを見て、今iAmThisがあります。

ここではすべてが少し複雑です:I​​Amクラスがあり、それはデータクラスです(これはKotlinの素晴らしい機能であり、等しい、hashCode、toString、およびすべてのこのボイラープレートを自動的に生成します。 Scalaでは、これはケースクラスです。実際、誰もが正確にデータクラスとして使用していますが、この名前はもっと悪いです。

IAmクラスには、フィールドfooを宣言するコンストラクターがあります。 Fooは同時にプロパティであるため、hello()関数とともに使用できます。

そこに文字列「bar」を渡し、hello関数を呼び出して、何が返されるかを確認します。

 package p8_iAmThis data class IAm(var foo: String) { fun hello() = foo.apply { return this } } println(IAm("bar").hello()) 

出力では何が得られますか?


開始します...バーを受け取ります



なんで?

Applyはトリッキーな拡張機能です。 ラムダを受け取り、その中にラムダを呼び出して、これに対していくつかのアクションを実行するオブジェクトを許可します。 したがって、これはバーです。 そして、こんにちははバーです。

この点で、KotlinはJavaScriptに似ています。 JavaScriptのように、Kotlinでは、これが何であるかがわからなくなった状態に到達できます。

一般的に、多くの便利な関数があります:また、let、with。



原則として、それらはすべてかなり異なります。

たとえば、applyは、絶対にすべての型(null不可)の拡張関数です。 ラムダを受け入れますが、このラムダは外部オブジェクトではなく内部Tにアタッチするため、非常にトリッキーです(このラムダには独自のTがあります)。 すなわち 関数はthisでこのラムダを呼び出し、thisを返します(これも役立つ場合があります)。

他にも機能があります。 コードは次のように修正できます。

 package p8_iAmThis data class IAm(var foo: String) { fun hello() = foo.let { return it } } println(IAm("bar").hello()) 

そうすると、目立たなくなります。

場合によっては、適用は非常に便利な機能です。 しかし、コードを非常にすばやく見ると(そして最初のバージョンの記録が使用されている場合)、混乱する可能性があります。

最初のオプションでは、次のようにコードを短縮できます(適用関数はこれ自体を返すため、何も変更されません)。

 data class IAm(var foo: String) { fun hello() = foo.apply { } } 

パズラー9


すでに知っているlet関数を見てみましょう。

カナダからケビン・モストが送ったこのパズル。 引数の符号(Int)を出力する単純な関数があります。

 package p9_weirdChaining // by Kevin Most @kevinmost fun printNumberSign(num; Int) { if (num < 0) { "negative" } else if (num > 0) { "positive" } else { "zero" }.let { println(it) } } printNumberSign(-2) printNumberSign(0) printNumberSign(2) 

そのようなコードは何を印刷しますか?


開始...出口で-ゼロ; ポジティブ。



問題は何ですか?

ifは実際には式です。 つまり、2つの式が取得され、letは2番目の式にのみ適用されます。

Kotlinでたくさん書いたが、このパズルは決定しなかった。 これはある種の地獄のようなトピックです。 前回のJPointカンファレンスでは、コンパイラのバグだとさえ考えていました。 しかし、Andrei Breslavに尋ねたところ、これは単なるパーサーニュアンスであることがわかりました。

修正方法 簡単-かっこを付けるだけ:

 fun printNumberSign(num; Int) { (if (num < 0) { "negative" } else if (num > 0) { "positive" } else { "zero" }).let { println(it) } } 

次に、letを式全体の結果に適用します。 最初のケースでは、コードは次のように機能しました。

 fun printNumberSign(num; Int) { if (num < 0) { "negative" } else (if (num > 0) { "positive" } else { "zero" }).let { println(it) } } 

同時に、上の式は別々になります-let関数はそれに適用されません。
Kotlinにはelseif演算子はありません(存在する場合、このパズルは存在しませんでした)。

すべてのパズルゲームと同様に、モラルはこれです。そのようなコードを書かないでください。 複雑な操作(ここなど)を行う場合は、かっこを入れるか、変数に入れてからletを呼び出してください。

パズラー10


さらに面白いパズルゲーム。 たくさんのコードがあります。

このパズルゲームは、ダニエル・ボドピャンのザブミティルです。 これは、Kotlinの非常にクールな機能-デリゲートプロパティのパズルです。 Kotlinでは、たとえば、クラスに複数のプロパティがあり、それらがフィールドとしてではなく、マップマップルックアップとして実装されていることを宣言できます。

クラスPopulation-母集団があります。 そして、都市は私たちに与えます(var city:Map <String、Int>)そして、それらをこのマップに委任します。

これにより、実際にKotlinをJavaScriptに変換し、データを前後にコピーすることなく、より動的な構造を作成できます。 このようなクラスは多くのコードを削減します。

次に、Populationクラスのインスタンスを作成し、すべての都市の人口をそのクラスに渡します。

今、何年も経ったと想像してください。 人々は地球を汚した-火星に住むために飛び去った。 したがって、人口のある地図を破棄します。

前に見たwith関数があります。 母集団を取得し、それに関連する使用可能なフィールドを解決します(原則として、適用と同様)。

 package p10_mappedDelegates // by Daniil Vodopian @voddan class Population(var cities: Map<String, Int>) { val tallinn by cities val kronstadt by cities val st_petersburg by cities } val population = Population(mapOf( "st_petersburg" to 5_281_579, "tallinn" to 407_947, "kronstadt" to 43_005 )) // Many years have passed, now all humans live on Mars population.cities = emptyMap() with(population) { println("$tallinn; $kronstadt; $st_petersburg") } 

すべてが簡単です。 誰もが火星に飛ぶときに私たちの地球に何が起こるかを理解するだけです。 そのようなコードは何を生成しますか?


私たちは始めます...人々はどこにも姿を消していないことがわかります(火星に住むことは非常に難しいので、私たちは地球にとどまるでしょう)。



なんで?

Population.cities = emptyMap()はクラスの空のマップを作成しますが、インスタンスのマップは作成しません。 このようなコードを変更する場合(MutableMapを作成し、Kronstadtをpopulation.kronstadt = 0にリセットします):

 class Population(var cities: MutableMap<String, Int>) { val tallinn by cities var kronstadt by cities val st_petersburg by cities } val population = Population(mutablemapOf( "st_petersburg" to 5_281_579, "tallinn" to 407_947, "kronstadt" to 43_005 )) // Many years have passed, now all humans live on Mars population.kronstadt = 0 

コードは407947を出力します。 0; 5281579

しかし、私たちはまだ最初のオプション(cpopulation.cities = emptyMap())について議論しています。

デリゲートを実行すると、マップ参照が(それぞれの)ゲッター内に記憶されます。 また、都市へのリンクを変更しても、ゲッター内のリンクは変更されません。 しかし、私たちは都市内の都市に何か他のものを置くことさえできます、そしてそれは同じ地図へのリンクのままであるので、すべては機能します。 しかし、別のマップへの参照を変更すると、そのマップは機能しなくなります。

パズラー11


エストニアでは、「良い子には多くの名前があります」という優れた格言があります。

ここで、これがどのようにクラスに適用されるかを見てみましょう。

Kotlinにはこのような奇妙なニュアンスがあります。デフォルトのクラスはfinalであり、拡張することはできません。 まだそれらを拡張することができるオープンなキーワードがあります。

クラスCのこのパズルには、openメソッドがあります(そのため、認証できます)。 ここでは、xとyを使用します(これらにはデフォルト値があります-これは言語の非常に優れた機能です)。

クラスCを拡張し、sum関数をオーバーライドするクラスDがありますが、原則として、スーパー実装を引き起こすことを除いて、有用なことは何もありません。

次に変数dがあります-クラスDのインスタンスを作成します。 変数cがあり、同じインスタンスを割り当てます(クラスDの同じインスタンスへの2つの参照を取得します)。 そして、基本的に同じオブジェクトで同じメソッドを呼び出します。

 package p11_goodChildHasManyNames open class C { open fun sum(x: Int = 1, y: Int = 2): Int = x + y } class D : C() { override fun sum(y: Int, x: Int): Int = super.sum(x, y) } val d: D = D() val c: C = d print(c.sum(x = 0)) print(d.sum(x = 0)) println() 

最後に何が得られますか?


始めます...正解は21です。



何が起こっているのかを理解するのに役立ついくつかの警告がまだここにあります。

どちらの場合も、ポリモーフィズムのため、オーバーライドされた関数が呼び出されます。 実際にはcとdの両方がクラスDのインスタンスであるため、実行時に呼び出される関数が選択されます。ただし、JVMには名前パラメーターなどの機能がないため、コンパイル時コンパイラーはそれらを解決します。 すなわち 実行時に関数が選択されて呼び出され、コンパイル時にパラメーターが選択されることがわかります。 したがって、置換するパラメーターは、実行時に取得したオブジェクトではなく、変数のタイプによって異なります。 これはわきです。 名前を混同してはならないことを警告および警告します。関数をオーバーライドするときは、別の方法で呼び出す必要があります。

良いニュースは、提示されたパズルの約半分について、IDEAはすでに警告を発していることです。 JetBrains自体もツールに従事しているという事実のため、多くのエラーを回避するのに非常に役立ちます。 しかし、すべてではありません。 一部のパズルでは、警告はまったく不可能です。

ただし、言語は進化しています。 2016年、私が書き始めたばかりのとき、IDEAでの検査ははるかに少なく、それらのパズルをまとめるのははるかに簡単でした。 現在、状況はまったく異なります。バージョン1.1がリリースされ、多くのパッチリリースがあり、多くの検査がIDEAに追加され、Kotlinで正しく簡単に記述できるようになりました。

結論の代わりに、コトリンに行きましょう。


新しいパズルを送って、次のカンファレンスで楽しみましょう。



プログラミングインテリアが大好きで、Kotlinに完全に没頭したい場合は、次のMobius 2017モスクワ会議で発表されるこれらのレポートに注意することをお勧めします

Source: https://habr.com/ru/post/J338924/


All Articles