Kotlinとそのほぼ言語構成体

Javaを使用している開発者、特にAndroid開発者の多くは、すでにKotlinを知っています。 そうでなければ、見つけるのに遅すぎることはありません。 特に、Javaが言語のようなものとしてあなたに合わない場合(おそらくそうです)、またはあなたがScalaを所有しているが、この言語があなたに合わない場合、これも可能です。



要するに、Kotlinは静的に型付けされたJVM指向の言語であり、Android(Javaバイトコードにコンパイル)とWeb(JavaScriptにコンパイル)です。 言語開発者のJetBrainsは、簡潔で理解しやすい構文、コードの高速コンパイル、およびタイプセーフを目指しました。 この言語はまだプレリリース状態ですが、すべてが急速にリリースに向かっています。



ちなみに、Javaの後、Kotlinでの「再トレーニング」は難しくありません。これにより、明確な(主観的な)構文と、両方向のJavaコードとの完全な互換性の両方が得られます。



言語開発者のもう1つの目標は、 DSLに似たライブラリの作成や独自の構成(HTMLのタイプセーフビルダーの良い例、 yield実装に関する記事 )など、柔軟な使用の可能性でした。 Kotlinには、これらの問題を効率的かつ美しく解決できるいくつかの機能があります。 それらを知りましょう。



拡張機能



Kotlinには、拡張関数(およびプロパティ)を継承せずに、任意のクラスの機能を補完する機能があります。 たとえば、C#にも同じ機会があります。 拡張関数の動作はメンバー関数とは異なることに注意してください。拡張関数への呼び出しは、宣言された型に応じて静的に解決され、事実上ではありません。



例:



fun String.words(): List<String> { return this.split("\\W".toRegex()) } //  ,    return statement fun <T> List<T>.rotate(n: Int): List<T> = drop(n) + take(n) val str = "a quick brown fox jumps over the lazy dog" val words = s.words() val yoda = words.rotate(5) println(yoda.joinToString(" ") // over the lazy dog a quick brown fox jumps
      
      





例のtoRegex()、drop(n)、take(n)、joinToString( "")も拡張関数です。



関数を呼び出すための代替構文



1.引数が1つだけのインスタンス関数または拡張関数は、中置形式で呼び出すことができます。



 val squares = (1..100) map { i -> i * i } // (1..100).map({i -> i * i }) val multOfThree = squares filter { it % 3 == 0 } //it    -      
      
      





2.関数の最後の引数に関数型がある場合、関数を呼び出すときに、括弧の後ろに対応するラムダ式を中括弧だけで記述できます。



 val list = arrayListOf(1, 2, 3) with(list) { add(4) add(5) add(6) removeIf { it % 2 == 0 } } // with(list, { ... })
      
      





インライン関数



Kotlinには、関数にタグを付ける@inline



注釈があります。 その後、この関数のコードとその関数引数をコンパイルするときに、呼び出しの場所で置き換えられます。 一方で、これはいくつかの新しい可能性(非ローカルリターン、具体化されたジェネリック)を提供し、他方で、本体内のインライン関数の関数引数は、他のインライン関数にのみ呼び出しまたは渡すことができるという制限があります。 @inline



の主なアクションはパフォーマンスに関するものです。発生する関数呼び出しが少なくなり、重要なことに、匿名クラスとそのオブジェクトはラムダ式ごとに作成されません。



同じライブラリやライブラリのような標準ライブラリの拡張機能のほとんど。



小さな例:



 @inline fun <T> Iterable<T>.withEach(action: T.() -> Unit) = forEach { it.action() } //  : var i = 0 val lists = (0..5) map { ArrayList<Int>() } lists.withEach { add(++i) }
      
      





このコードはラムダ式でいっぱいであるという事実にもかかわらず、単一の匿名クラスがラムダ式のために作成されることはなく、 i



も閉鎖に陥ることはありません。 ただの休日!



やってみますか?



このすべての武器で何ができるかを見てみましょう-このようなかなり役に立たないデザインを作りたいとしましょう:



 val a = someInt() val b = someList() val c = (a % b.size()) butIf (it < 0) { it + b.size() } // (a % b.size()) let { if (it < 0) it + b.size() else it }
      
      





残念ながら、これは正しく機能しませんが、同様のことをしようとします。



最初の試行:2つの関数引数を持つ関数



 fun <T> T.butIf(condition: (T) -> Boolean, thenFunction: (T) -> T): T { if (condition(this)) { return thenFunction(this) } return this }
      
      





これがどのように使用できるかです:



 val c = (a % b.size()).butIf({it < 0}) {it + b.size()}
      
      





ここにインラインを追加すると、非常に効率的になります。 後で、このデザインのより美しい構文をどのように達成するかを今から見ていきます。



2回目の試み:美しい構文



 abstract class _ButIfPrefix<T> constructor(var originalValue: T) { abstract fun then(thenFunction: (T) -> T): T object trueBranch : _ButIfPrefix<Any?>(null) { override final inline fun then(thenFunction: (Any?) -> Any?) = thenFunction(originalValue) } object falseBranch : _ButIfPrefix<Any?>(null) { override final inline fun then(thenFunction: (Any?) -> Any?) = originalValue } } fun <T> T.butIf(condition: (T) -> Boolean): _ButIfPrefix<T> { val result = (if (condition(this)) _ButIfPrefix.trueBranch else _ButIfPrefix.falseBranch) as _ButIfPrefix<T> result.originalValue = this return result }
      
      





このオプションはマルチスレッド用に設計されていません! 複数のスレッドで使用するには、ThreadLocalでインスタンスをラップする必要があります。これにより、パフォーマンスがわずかに低下します。



2つの中置呼び出しのチェーンがあります。最初はオブジェクト自体の拡張関数、2番目は_ButIfPrefix



インスタンス_ButIfPrefix



です。 使用例:



 val c = (a % b.size()) butIf { it < 0 } then { it + b.size() }
      
      





3番目の試み:カレー



これを試してみましょう:



 fun <T> T.butIf0(condition: (T) -> Boolean): ((T) -> T) -> T { return inner@ { thenFunction -> return@inner if (condition(this)) thenFunction(this) else this } }
      
      





使用法:



 val c = (a % b.size()).butIf { it < 0 } ({ it + b.size() })
      
      





最初の試行と比較して、呼び出しの括弧の位置が変更されています。 :)

インラインの場合、このオプションは最初のオプションと同じように機能することが期待できます。

これは、バイトコードを調べることで確認できます@inline



は、Kotlinコードがオンザフライでコンパイルされるバイトコードを表示するユーティリティがあり、 @inline



有無でバイトコードがどのように異なるかを確認することもできます。



性能



さまざまな方法で設計のパフォーマンスがどうなるかを見てみましょう。



次の例でテストします。



 val range = -20000000..20000000 val list = ArrayList<Int>() //warm-up for (i in range) { list add i % 2 } list.clear() val timeBefore = System.currentTimeMillis() for (i in range) { val z = (i % 2) butIf { it < 0 } then { it + 2 } //  list add z } println("${System.currentTimeMillis() - timeBefore} ms")
      
      





比較に戻って、パフォーマンスのベンチマークとなるコードを追加します。



 ... val d = it % 2 val z = if (d < 0) d + 2 else d ...
      
      





50回の打ち上げとその後の時間平均の結果によると、次の表が得られます。



実装 インラインなし Cインライン
標準 319ミリ秒
試みる 406ミリ秒 348ミリ秒
IIの試み 610ミリ秒 520ミリ秒
II ThreadLocalでの試行 920ミリ秒 876ミリ秒
IIIの試み 413ミリ秒 399ミリ秒


ご覧のとおり、よりシンプルな1番目と3番目のオプションのパフォーマンスは標準に非常に近く、場合によっては、このような操作時間の増加に対してコードを読みやすくすることができます。 より美しい構文を持つバリアントはより複雑で、それぞれ長く機能しますが、DSLに完全に類似した構造が必要な場合は、非常に適用可能です。



合計



Kotlinは、言語を「カスタマイズ」するための真に柔軟なオプションを提供しますが、パフォーマンスに応じて料金を支払う必要がある場合があります。 @inline



は、コードに1次関数がある場合に状況を改善するのに役立ちます。 いずれにせよ、このすべてに適したユースケースが見つかると思います。



頑張って



All Articles