コトリンを詳しく芋おみたしょう

画像


https://trends.google.com/trends/explore?q=%2Fm%2F0_lcrx4


䞊蚘は「kotlin」ずいう単語を怜玢したずきのGoogleトレンドのスクリヌンショットです。 突然の急増は、 GoogleがKotlinがAndroidの䞻芁蚀語になっおいるこずを発衚したずきです 。 これは数週間前のGoogle I / Oカンファレンスで起こりたした。 これたで、あなたはあなたの呚りの誰もが突然それに぀いお話し始めたので、あなたは以前にこの蚀語を䜿甚したか、たたはそれに興味を持ちたした。


Kotlinの䞻な機胜の1぀は、Javaずの盞互運甚性です。JavaからKotlinコヌドを呌び出し、KotlinからJavaコヌドを呌び出すこずができたす。 これはおそらく、蚀語が広く配垃されおいるため、最も重芁な機胜です。 すべおをすぐに移行する必芁はありたせん。既存のコヌドベヌスの䞀郚を取埗しお、Kotlinコヌドの远加を開始するだけで機胜したす。 Kotlinを詊しおみお、気に入らない堎合は、い぀でも拒吊できたすただし、詊しおみるこずをお勧めしたす。


Javaで5幎間働いた埌、初めおKotlinを䜿甚したずき、いく぀かのこずが魔法のように思えたした。


「ちょっず埅っお、䜕 定型コヌドを回避するためにdata classを蚘述するこずはできたすか」
「やめお、 applyを曞いたら、それに関しおメ゜ッドを呌び出すたびにオブゞェクトを定矩する必芁はありたせんか」


時代遅れで面倒ではない蚀語がようやく登堎したずいう事実からの最初の安ighのため、私は䞍快感を感じ始めたした。 Javaずの盞互互換性が必芁な堎合、これらすべおの優れた機胜はKotlinでどの皋床正確に実装されたすか キャッチは䜕ですか


この蚘事は専甚です。 Kotlinコンパむラヌが特定のコンストラクトをどのように倉換しおJavaずの互換性を持たせるかを孊ぶこずに非垞に興味がありたした。 私の研究では、Kotlin暙準ラむブラリから最も䞀般的な4぀の方法を遞択したした。


  1. apply
  2. with
  3. let
  4. run

この蚘事を読むず、恐れる必芁がなくなりたす。 今、私はすべおがどのように機胜するかを理解し、蚀語ずコンパむラを信頌できるこずを知っおいるので、はるかに自信を感じおいたす。


適甚する


 /** *    [block]   `this`        `this`. */ @kotlin.internal.InlineOnly public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this } 

applyは単玔です。これは、拡匵型むンスタンス「レシヌバヌ」ず呌ばれるに関しおブロックパラメヌタヌを実行し、レシヌバヌ自䜓を返す拡匵関数です。


この機胜を適甚するには倚くの方法がありたす。 オブゞェクトの䜜成を初期構成にバむンドできたす。


val layout = LayoutStyle().apply { orientation = VERTICAL }


ご芧のように、䜜成時に新しいLayoutStyle構成を提䟛したす。これにより、 コヌドがクリヌンになり、 実装の゚ラヌがはるかに少なくなりたす。 同じ名前であるため、間違ったむンスタンスでメ゜ッドを呌び出したしたか さらに悪いこずに、リファクタリングに完党に欠陥があったずきは 䞊蚘のアプロヌチでは、このような問題に盎面するのがはるかに困難になりたす。 たた、 thisパラメヌタヌを定矩する必芁がないこずに泚意しおください。 クラス自䜓ず同じスコヌプ内にいたす 。 クラス自䜓を拡匵しおいるかのようであるため、 thisは暗黙的に指定されたす。


しかし、それはどのように機胜したすか 短い䟋を芋おみたしょう。


 enum class Orientation { VERTICAL, HORIZONTAL } class LayoutStyle { var orientation = HORIZONTAL } fun main(vararg args: Array<String>) { val layout = LayoutStyle().apply { orientation = VERTICAL } } 

IntelliJ IDEA Show Kotlinバむトコヌドツヌル[ Tools > Kotlin > Show Kotlin Bytecode Kotlinバむトコヌドの衚瀺]のおかげで、コンパむラがコヌドをJVMバむトコヌドに倉換する方法を確認できたす。


 NEW kotlindeepdive/LayoutStyle DUP INVOKESPECIAL kotlindeepdive/LayoutStyle.<init> ()V ASTORE 2 ALOAD 2 ASTORE 3 ALOAD 3 GETSTATIC kotlindeepdive/Orientation.VERTICAL : Lkotlindeepdive/Orientation; INVOKEVIRTUAL kotlindeepdive/LayoutStyle.setOrientation (Lkotlindeepdive/Orientation;)V ALOAD 2 ASTORE 1 

バむトコヌドの指向があたりよくない堎合は、 これらの 玠晎らしい 蚘事を読むこずをお勧めしたす。その埌、よりよく理解するこずができたす各メ゜ッドはスタックで呌び出されるため、コンパむラは毎回オブゞェクトをロヌドする必芁があるこずに泚意するこずが重芁です。


ポむントを分析したしょう


  1. LayoutStyle新しいむンスタンスLayoutStyleスタックに耇補LayoutStyleたす。
  2. パラメヌタヌがれロのコンストラクタヌが呌び出されたす。
  3. ストア/ロヌド操䜜が実行されたす以䞋を参照。
  4. 倀Orientation.VERTICALスタックに枡されたす。
  5. setOrientationがsetOrientationれ、スタックからオブゞェクトず倀が発生したす。

ここで泚意すべき点がいく぀かありたす。 たず、魔法は䞀切関係なく、すべおが期埅どおりに行わsetOrientationたす。䜜成したLayoutStyleむンスタンスに関連しお、 LayoutStyleメ゜ッドがsetOrientationたす。 さらに、コンパむラヌがむンラむン化するため、 apply関数はどこにも衚瀺されたせん。


さらに、バむトコヌドは、Javaのみを䜿甚した堎合に生成されるものずほが同じです 自分で刀断する


 // Java enum Orientation { VERTICAL, HORIZONTAL; } public class LayoutStyle { private Orientation orientation = HORIZONTAL; public Orientation getOrientation() { return orientation; } public void setOrientation(Orientation orientation) { this.orientation = orientation; } public static void main(String[] args) { LayoutStyle layout = new LayoutStyle(); layout.setOrientation(VERTICAL); } } // Bytecode NEW kotlindeepdive/LayoutStyle DUP ASTORE 1 ALOAD 1 GETSTATIC kotlindeepdive/Orientation.VERTICAL : kotlindeepdive/Orientation; INVOKEVIRTUAL kotlindeepdive/LayoutStyle.setOrientation (kotlindeepdive/Orientation;)V 

ヒント倚数のASTORE/ALOAD気づいたかもしれたせん。 これらはKotlinコンパむラヌによっお挿入されるため、デバッガヌはラムダでも機胜したす これに぀いおは、蚘事の最埌のセクションで説明したす。


ず


 /** *    [block]   [receiver]       . */ @kotlin.internal.InlineOnly public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block() 

applyに䌌おいapply 、いく぀かの重芁な違いがありたす。 たず、 with型の拡匵関数ではありたせん。レシヌバヌはパラメヌタヌずしお明瀺的に枡される必芁がありたす。 さらに、 withはブロック関数の結果を返し、 applyはレシヌバヌ自䜓の結果を返したす。


䜕でも返すこずができるので、この䟋は非垞に信じられそうです。


 val layout = with(contextWrapper) { // `this` is the contextWrapper LayoutStyle(context, attrs).apply { orientation = VERTICAL } } 

ここでは、 contextWrapperプレフィックスを省略できたす。 contextWrapperはwith関数のレシヌバヌであるため、 contextおよびattrs堎合。 しかし、この堎合でも、適甚方法はapplyず比范するずそれほど明癜ではありたせん。この関数は特定の条件䞋で圹立぀堎合がありたす。


これが䞎えられたので、䟋に戻り、以䞋で䜿甚with堎合に䜕が起こるかを芋おみたしょう


 enum class Orientation { VERTICAL, HORIZONTAL } class LayoutStyle { var orientation = HORIZONTAL } object SharedState { val previousOrientation = VERTICAL } fun main() { val layout = with(SharedState) { LayoutStyle().apply { orientation = previousOrientation } } } 

をwithレシヌバヌはSharedStateシングルトンであり、レむアりトに蚭定する方向パラメヌタヌが含たれおいたす。 ブロック関数内で、 LayoutStyleむンスタンスを䜜成したす。 applyおかげで、 SharedStateから読み取るこずで簡単に方向を蚭定できapply 。


生成されたバむトコヌドをもう䞀床芋おみたしょう。


 GETSTATIC kotlindeepdive/SharedState.INSTANCE : Lkotlindeepdive/SharedState; ASTORE 1 ALOAD 1 ASTORE 2 NEW kotlindeepdive/LayoutStyle DUP INVOKESPECIAL kotlindeepdive/LayoutStyle.<init> ()V ASTORE 3 ALOAD 3 ASTORE 4 ALOAD 4 ALOAD 2 INVOKEVIRTUAL kotlindeepdive/SharedState.getPreviousOrientation ()Lkotlindeepdive/Orientation; INVOKEVIRTUAL kotlindeepdive/LayoutStyle.setOrientation (Lkotlindeepdive/Orientation;)V ALOAD 3 ASTORE 0 RETURN 

特別なこずは䜕もありたせん。 SharedStateクラスの静的フィヌルドずしお実装された取埗されたシングルトン。 以前ず同様にLayoutStyleのむンスタンスが䜜成され、コンストラクタヌが呌び出されたすSharedState内でpreviousOrientation倀を取埗する別の呌び出しSharedStateおよびLayoutStyleむンスタンスに倀を割り圓おる最埌の呌び出し。


ヒント「Show Kotlin Bytecode」を䜿甚する堎合、「Decompile」をクリックするず、Kotlinコンパむラヌ甚に䜜成されたバむトコヌドのJava衚珟を確認できたす。 ネタバレそれはたさにあなたが期埅するものです


させる


 /** *    [block]   `this`      . */ @kotlin.internal.InlineOnly public inline fun <T, R> T.let(block: (T) -> R): R = block(this) 

let 、nullになる可胜性があるオブゞェクトを操䜜するずきに非垞に䟿利です。 if-else匏の無限のチェヌンを䜜成する代わりに、単玔にステヌトメントを結合できたす? 「安党な呌び出しステヌトメント」ず呌ばれる letを䜿甚するず、結果ずしお、匕数が元のオブゞェクトのnull䞍可バヌゞョンであるラムダを取埗したす。


 val layout = LayoutStyle() SharedState.previousOrientation?.let { layout.orientation = it } 

䟋党䜓を考えおみたしょう。


 enum class Orientation { VERTICAL, HORIZONTAL } class LayoutStyle { var orientation = HORIZONTAL } object SharedState { val previousOrientation: Orientation? = VERTICAL } fun main() { val layout = LayoutStyle() // layout.orientation = SharedState.previousOrientation -- this would NOT work! SharedState.previousOrientation?.let { layout.orientation = it } } 

これで、 previousOrientationはnullになる堎合がありたす。 レむアりトに盎接割り圓おようずするず、null蚱容型をnon-null蚱容型に割り圓おるこずができないため、コンパむラが怒りたす。 もちろん、if匏を蚘述できたすが、これによりSharedState.previousOrientation匏ぞの二重参照がSharedState.previousOrientationたす。 letを䜿甚するず、レむアりトに安党に割り圓おるこずができる同じパラメヌタヌぞのnull䞍可の参照を取埗したす。
バむトコヌドの芳点から芋るず、すべおが非垞に単玔です。


 NEW kotlindeepdive/let/LayoutStyle DUP INVOKESPECIAL kotlindeepdive/let/LayoutStyle.<init> ()V GETSTATIC kotlindeepdive/let/SharedState.INSTANCE : Lkotlindeepdive/let/SharedState; INVOKEVIRTUAL kotlindeepdive/let/SharedState.getPreviousOrientation ()Lkotlindeepdive/let/Orientation; DUP IFNULL L2 ASTORE 1 ALOAD 1 ASTORE 2 ALOAD 0 ALOAD 2 INVOKEVIRTUAL kotlindeepdive/let/LayoutStyle.setOrientation (Lkotlindeepdive/let/Orientation;)V GOTO L9 L2 POP L9 RETURN 

単玔なIFNULL条件付き遷移を䜿甚したす。これは、本質的には手動で行う必芁がありたす。ただし、コンパむラが効率的に実行する堎合を陀き、蚀語はこのようなコヌドを䜜成する優れた方法を提䟛したす。 これは玠晎らしいず思いたす


走る


runには2぀のバヌゞョンがありたす。1぀目は単玔な関数、2぀目はゞェネリック型の拡匵関数です。 最初の関数はパラメヌタヌずしお枡されるブロック関数のみを呌び出すため、2番目の関数を分析したす。


 /** *    [block]   `this`      . */ @kotlin.internal.InlineOnly public inline fun <T, R> T.run(block: T.() -> R): R = block() 

おそらく、 runは、考えられる最も単玔な関数です。 これは、むンスタンスがレシヌバヌずしお枡されるタむプの拡匵関数ずしお定矩され、 block関数の実行結果を返したす。 runはletずapplyハむブリッドのように思えるかもしれたせんが、実際はそうです。 唯䞀の違いは戻り倀です。 applyの堎合は受信者自身を返し、 runの堎合はblock関数の結果を返したす letの堎合ず同様。


この䟋では、 runがblock関数の結果を返すずいう事実を匷調しおrunたす。この堎合、それは割り圓お Unit です。


 enum class Orientation { VERTICAL, HORIZONTAL } class LayoutStyle { var orientation = HORIZONTAL } object SharedState { val previousOrientation = VERTICAL } fun main() { val layout = LayoutStyle() layout.run { orientation = SharedState.previousOrientation } // returns Unit } 

同等のバむトコヌド


 NEW kotlindeepdive/LayoutStyle DUP INVOKESPECIAL kotlindeepdive/LayoutStyle.<init> ()V ASTORE 0 ALOAD 0 ASTORE 1 ALOAD 1 ASTORE 2 ALOAD 2 GETSTATIC kotlindeepdive/SharedState.INSTANCE : Lkotlindeepdive/SharedState; INVOKEVIRTUAL kotlindeepdive/SharedState.getPreviousOrientation ()Lkotlindeepdive/Orientation; INVOKEVIRTUAL kotlindeepdive/LayoutStyle.setOrientation (Lkotlindeepdive/Orientation;)V RETURN 

runは他の関数ず同様にむンラむンであり、単玔なメ゜ッド呌び出しになりたした。 ここでも奇劙なこずはありたせん




暙準ラむブラリの機胜には倚くの類䌌点があるこずに泚意したした。これは、できるだけ倚くのアプリケヌションをカバヌするために意図的に行われたした。 䞀方、特定のタスクにどの関数が最適であるかを理解するこずは、それらの違いがわずかであるこずを考えるず、それほど簡単ではありたせん。


暙準ラむブラリを扱うのを助けるために、考慮された䞻な機胜間のすべおの違いを芁玄した衚を描きたした䟋倖もありたす 


画像


アプリケヌション远加のstore/load操䜜


「Javaバむトコヌド」ず「Kotlinバむトコヌド」を比范するず、ただ完党に理解できたせんでした。 私が蚀ったように、Kotlinでは、Javaずは異なり、远加の操䜜astore/aload 。 これはラムダず関係があるこずは知っおいたしたが、なぜラムダが必芁なのか理解できたした。


デバッガヌがラムダをスタックフレヌムずしお凊理するには、これらの远加の操䜜が必芁であるように思われたす 。これにより、䜜業に介入ステップむンできたす。 ロヌカル倉数が䜕であるか、誰がラムダを呌び出しおいるか、誰がラムダから呌び出されるかなどを確認できたす。


しかし、APKを実皌働環境に枡すずき、デバッガの機胜は気にしたせんか そのため、これらの関数は、サむズが小さく重芁でないにもかかわらず、冗長であり、削陀される可胜性があるず考えるこずができたす。


このためには、すべおの人に知られ、誰もが愛するツヌルであるProGuardが適しおいたす。 バむトコヌドレベルで動䜜し、難読化ずトリミングに加えお、最適化パスを実行しおバむトコヌドをよりコンパクトにしたす。 JavaずKotlinで同じコヌドを䜜成し、1぀のルヌルセットを䜿甚しおProGuardの䞡方のバヌゞョンに適甚し、結果を比范したした。 これが䜕が起こったのかです。


ProGuardの構成


 -dontobfuscate -dontshrink -verbose -keep,allowoptimization class kotlindeepdive.apply.LayoutStyle -optimizationpasses 2 -keep,allowoptimization class kotlindeepdive.LayoutStyleJ 

゜ヌスコヌド


Java


 package kotlindeepdive enum OrientationJ { VERTICAL, HORIZONTAL; } class LayoutStyleJ { private OrientationJ orientation = HORIZONTAL; public OrientationJ getOrientation() { return orientation; } public LayoutStyleJ() { if (System.currentTimeMillis() < 1) { main(); } } public void setOrientation(OrientationJ orientation) { this.orientation = orientation; } public OrientationJ main() { LayoutStyleJ layout = new LayoutStyleJ(); layout.setOrientation(VERTICAL); return layout.orientation; } } 

コトリン


 package kotlindeepdive.apply enum class Orientation { VERTICAL, HORIZONTAL } class LayoutStyle { var orientation = Orientation.HORIZONTAL init { if (System.currentTimeMillis() < 1) { main() } } fun main() { val layout = LayoutStyle().apply { orientation = Orientation.VERTICAL } layout.orientation } } 

バむトコヌド


Java


  sgotti@Sebastianos-MBP ~/Desktop/proguard5.3.3/lib/PD/kotlindeepdive > javap -c LayoutStyleJ.class Compiled from "SimpleJ.java" final class kotlindeepdive.LayoutStyleJ { public kotlindeepdive.LayoutStyleJ(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: aload_0 5: getstatic #6 // Field kotlindeepdive/OrientationJ.HORIZONTAL$5c1d747f:I 8: putfield #5 // Field orientation$5c1d747f:I 11: invokestatic #9 // Method java/lang/System.currentTimeMillis:()J 14: lconst_1 15: lcmp 16: ifge 34 19: new #3 // class kotlindeepdive/LayoutStyleJ 22: dup 23: invokespecial #10 // Method "<init>":()V 26: getstatic #7 // Field kotlindeepdive/OrientationJ.VERTICAL$5c1d747f:I 29: pop 30: iconst_1 31: putfield #5 // Field orientation$5c1d747f:I 34: return } 

コトリン


  sgotti@Sebastianos-MBP ~/Desktop/proguard5.3.3/lib/PD/kotlindeepdive > javap -c apply/LayoutStyle.class Compiled from "Apply.kt" public final class kotlindeepdive.apply.LayoutStyle { public kotlindeepdive.apply.LayoutStyle(); Code: 0: aload_0 1: invokespecial #13 // Method java/lang/Object."<init>":()V 4: aload_0 5: getstatic #11 // Field kotlindeepdive/apply/Orientation.HORIZONTAL:Lkotlindeepdive/apply/Orientation; 8: putfield #10 // Field orientation:Lkotlindeepdive/apply/Orientation; 11: invokestatic #14 // Method java/lang/System.currentTimeMillis:()J 14: lconst_1 15: lcmp 16: ifge 32 19: new #8 // class kotlindeepdive/apply/LayoutStyle 22: dup 23: invokespecial #16 // Method "<init>":()V 26: getstatic #12 // Field kotlindeepdive/apply/Orientation.VERTICAL:Lkotlindeepdive/apply/Orientation; 29: putfield #10 // Field orientation:Lkotlindeepdive/apply/Orientation; 32: return } 

2぀のバむトコヌドリストを比范した埌の結論


  1. 「Kotlinバむトコヌド」の远加のastore/aloadは消えたしたastore/aloadはそれらを冗長であるず刀断し、すぐに削陀したためですこのため、1぀が削陀されなかった埌、2぀の最適化パスを䜜成する必芁がありたした。
  2. JavaバむトコヌドずKotlinバむトコヌドはほが同じです。 最初のものは列挙倀を操䜜するずきに面癜い/奇劙な瞬間がありたすが、Kotlinにはそのようなものはありたせん。

おわりに


開発者に非垞に倚くの機胜を提䟛する新しい蚀語を取埗するこずは玠晎らしいこずです。 しかし、䜿甚するツヌルに頌るこずができるこずを理解し、それらを䜿甚するずきに自信を持っおいるこずも重芁です。 コンパむラが䜙分なこずや危険なこずを䜕もしないこずを知っおいるずいう意味で、「私はKotlinを信頌しおいたす」ず蚀うこずができおうれしいです。 Javaで手動で行う必芁があるこずだけを行い、時間ずリ゜ヌスを節玄したすそしお、JVMのコヌディングの長幎の喜びを返したす。 これは、゚ンドナヌザヌにずっおもある皋床のメリットがありたす。より厳密な型の安党性により、アプリケヌションのバグが少なくなるからです。


さらに、Kotlinコンパむラヌは絶えず改善されおいるため、生成されたコヌドはより効率的になっおいたす。 そのため、コンパむラを䜿甚しおKotlinコヌドを最適化する必芁はありたせん。より効率的で慣甚的なコヌドの䜜成に専念し、残りはコンパむラに任せるこずをお勧めしたす。



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


All Articles