自動コンパイラオブジェクト

この記事はKotlinバージョン1.1-M04に基づいています。


この記事では、ユーザーコードの匿名ブロックに対してKotlinコンパイラーが自動クラスを生成するメカニズムについて説明します。 どのような場合に自動クラスが作成されるか、それらがどこにあるか、どのように実装および使用されるかについて説明されています。


Java 、コード要素を作成する方法は1つしかありません。これは、機能的なインターフェイスのみを実装できるラムダ式です(詳細については、 Javaドキュメントを参照してください)。 Kotlinには、カスタムコードを含むオブジェクトを記述する方法がいくつかあります。


Kotlinの機能は、このような構成を記述できるコードを非常に簡単に実装できることを誰もが知っています。


 fun Action( cb:()->Unit ) { cb() } fun Test() { Action{ //        } } 

これはどのように実装され、プログラムでそのようなコードを使用するリスクは何ですか?


PS:この記事の元のバージョンには、論理的なエラーと誤った結論が含まれていました。 この事実をおeveryoneび申し上げます。 この記事は改訂され、追加データで補足されました。



自動コンパイラオブジェクト


JVM別個のコードをJVMことは不可能であり、クラスでのみ操作できます。 状況によっては、 Kotlinコンパイラーは、指定されたコードが実行されるクラスを自動的に作成し、場合によっては直接呼び出します。 さまざまなケースで、さまざまなタイプのクラスが作成されます。


Kotlin使用すると、さまざまな構文を使用して匿名コードで構成を実装できます。 以下の例は、コンパイラーによるテストの実装方法ではなく、テストでのプログラムの使用方法によって分類されています。
実際には、リストされている構文構成のほとんどすべてが同じ方法で実装されています。


匿名関数


任意のパラメーターと戻り値を含むコードのブロック。直接呼び出したり、パラメーターとして関数に渡すことができます。


無名関数の例
 fun Action(cb : () -> Int) //  = println(cb()) class BB { fun F() = 1133 fun M() { Action(this::F) //     } } fun statMethod() = 1111 fun Test() { val cb = { 1122 } //     cb() //      Action(cb) //     Action { 1133 } //        Action(::statMethod) //      } 

構文「 ::statMethod 」および「 this::F 」は、構文を簡素化するために使用され、匿名コードで関数を手動で呼び出すことと完全に同等です。


  Action{ statMethod() } //  Action(::statMethod) Action{ F() } //  Action(this::F) 

オブジェクトの匿名関数


オブジェクトインスタンスのコンテキストで実行されるコードブロック、つまり このコードが呼び出されるオブジェクトを参照する「 this 」フィールドがあります。


Kotlinこのアプローチを実装する際、構文を簡素化するためにthisオブジェクトがエミュレートされますが、オブジェクトの暗黙的に渡されたインスタンスが実際に使用されます。 this存在し、明示的なオブジェクトを指定せずにクラスの要素にアクセスしても、実行されるコードはクラスの一部ではないため、その保護された要素にアクセスできないことに注意してください。


オブジェクトの匿名関数の例
 import java.awt.event.ActionEvent import java.awt.event.ActionListener fun <T> cAction(v : T, cb : T.() -> Int) //       = println(v.cb()) class BB { fun F() = 1133 //   fun M() { cAction(this, BB::F) //       } } fun Test() { cAction("text", String::length) //       } 

ラムダ関数


このインターフェイスの唯一のメソッドのために、自動生成されたクラス、機能インターフェイスの後継で実装されたブロック。


ラムダ関数の例
 fun add(i : ActionListener) {} //   fun Test() { add(ActionListener { }) //  ,   } 

匿名クラス


指定されたクラスから継承する機能を持つ、自動生成された名前のないクラス。


匿名クラスの例
 fun add(i : ActionListener) {} //   fun Test() { add(object : ActionListener {//  ,   override fun actionPerformed(e : ActionEvent?) {} }) } 


外部自動クラス


自動オブジェクトを宣言するための上記のすべてのメソッドを1つのソースファイルにまとめてコンパイルします。


テストコードの全文
 import java.awt.event.ActionEvent import java.awt.event.ActionListener fun add(i : ActionListener) {} fun <T> cAction(v : T, cb : T.() -> Int) = println(v.cb()) fun Action(cb : () -> Int) = println(cb()) fun statMethod() = 1111 class BB { fun F() = 1133 fun M() { //jm/test/ktest/BB$M$1.INSTANCE cAction(this, BB::F) //jm/test/ktest/BB$M$2 Action(this::F) //jm/test/ktest/BB$M$3 Action{F()} } } fun Test() { //jm/test/ktest/KMainKt$Test$1 add(object : ActionListener { override fun actionPerformed(e : ActionEvent?) {} }) //jm/test/ktest/KMainKt$Test$2.INSTANCE add(ActionListener { e-> }) //jm/test/ktest/KMainKt$Test$cb$1.INSTANCE val cb = { 1122 } cb() Action(cb) //jm/test/ktest/KMainKt$Test$3.INSTANCE Action { 1133 } //jm/test/ktest/KMainKt$Test$4.INSTANCE Action(::statMethod) //jm/test/ktest/KMainKt$Test$5.INSTANCE cAction("text", String::length) } 

コンパイラが作成されたクラスを保存したディレクトリを見ると、次のファイルを見ることができます。


ファイル名サイズ、バイト
BBクラス1150
KMainKt.class3169
BB $ M $ 1.クラス1710
BB $ M $ 2.クラス1379
BB $ M $ 3.クラス1025
KMainKt $ Test $ 1.class973
KMainKt $ Test $ 2.class910
KMainKt $テスト$ 3.class1029
KMainKt $テスト$ 4.class1450
KMainKt $テスト$ 5.class1222
KMainKt $テスト$ cb $ 1.クラス1035

最初の2つのファイルがどこから来たかは明らかです-これらは、プログラムで説明されているクラスです。


  • BB.class 」ファイルは、テキストで宣言されたクラスです。
  • KMainKt.classファイルはKotlinプログラムのメインクラスであり、クラスに含まれていないすべてのデータとメソッドが含まれています。 このファイルの名前は、ソースプログラムのファイル名(この例では「 kMain.kt 」)とサフィックス「 Kt 」で構成されています。 この例では、「 Action 」、「 cAction 」など、使用されるすべてのメソッドがこのクラスに配置されます。

残りのファイルはどこから来たのですか?


これらのファイルには、コンパイラーが自動的に生成したクラスのコードが含まれています。 上記のプログラムのテキストでは、コメントに、生成されたクラスの名前がプログラム内の宣言の場所に従って示されています。


注意:コンパイラがクラスの新しいインスタンスではなく、新しいCLASSを作成するたびに。 つまり プログラムでコードを使用するたびに、プログラム内の単一の場所で使用される新しいクラスが作成されます!


ソーステキストを見ると、一時オブジェクトの作成とは無関係に見えるテキストもクラスの生成につながることに注意してください。


  Action(::statMethod) // jm/test/ktest/KMainKt$Test$4.INSTANCE cAction("text", String::length) // jm/test/ktest/KMainKt$Test$5.INSTANCE 

これは、プログラムテキストで使用される構文を保証するために、コンパイラが指定されたメソッドを呼び出すラッパークラスを作成するだけだからです。


上記の表は、生成されたクラスファイルが、作成対象のコードブロックで実行されるコードに比例して非常に大きいことを示しています。 この例では実際に有用なコードのボリュームが事実上ゼロであるという事実にもかかわらず、作成されたクラスのボリュームはプログラム「 KMainKt.class 」のメインファイルのサイズのほぼ4倍です。 これは、多くの追加情報がクラスファイルに格納されるためです。クラスコードの量が少ない場合、クラスファイル内の有用な情報の量はその一部になります。


自動クラスが実行するコードが大きい場合、結果のファイルのボリュームは無視できます。 ほとんどの場合、自動クラスのコードが非常に小さい場合、プログラムで説明されている匿名ブロックごとに1〜1.5Kbのファイルの作成を有効または許容範囲で呼び出すことはかなり困難です。


自動生成されたクラスは、アセンブルされた形式のプログラムのサイズに直接影響します。 プログラムで匿名コードを積極的に使用すると、そのサイズは急速に増大する可能性があります。 多くの場合、プログラムのサイズは重要な役割を果たします。 これは、モバイルアプリケーションまたはWEBアプリケーション、およびパーソナルコンピューターで実行するように設計されたアプリケーションの両方に適用できます。


複製。 最適化?


別の実験を行ってみましょう。自動クラスを最適化するKotlin機能を確認してください。


この例でコンパイラがクラスを自動的に作成するすべての場所を考慮すると、クラスは一意のコードを持つブロックに対してのみ作成されるという事実に関連する最適化があり、他のすべての場合は既存のクラスのオブジェクトが使用されると想定できます。


最適化の可能性をテストするには、単に2行のコードを乗算します。 1つはカスタムコードを使用し、もう1つは完全自動コードを使用します。


 Action{} Action{} Action( ::statMethod ) Action( ::statMethod ) 

理想的には、 Kotlinは最初のグループと2番目のグループの両方で自動クラスの使用を最適化します。 良いバージョンでは-2番目のみ。 ユーザーコードの一意性を確認することは非常に困難なため、コンパイラが完全に自動モードで作成したクラスの基本的な最適化のみに依存します。


例の修正、コンパイル、クラスファイルの表示は独立した演習として行い、結果に直行します。


コード記述の場所ごとに新しい一意のクラスが作成されます!


残念ながら、 Kotlinの現在のバージョン(バージョン1.1-M04を使用)では、自動クラスの生成に関連する最適化はなく、コードを使用すると新しいクラスが作成されます。



「余分な」サイズに対処する方法は?


Kotlinプログラムでは、匿名コードの使用が非常に広く普及しており、間違いなくその記述と使用の容易さがこの言語の基本的な利点の1つです。 匿名コードのアプリケーションが豊富にあるため、各コードのクラスの生成によりプログラムのボリュームが増加するという問題は、非常に大きな問題になる可能性があります。


コンパイルされたアプリケーションでファイルに冗長情報を保存するには、2つの方法があります。


1つは、 JVM用にコンパイルされたファイルからサービス情報を削除するように設計されたさまざまなツールを使用することです。


このグループの非常に興味深いツールの1つがProGuardです。 これは、収集したJARアーカイブをアプリケーションとともに再パッケージ化し、そこからすべてのサービス情報を削除できるフリーウェアツールです。 単にサービス情報を削除するだけでなく、クラスコードを最適化するという素晴らしい仕事をします。 ボーナスとして、このツールは「 Obfuscatorツール」として使用できます。 コードからアプリケーションロジックを回復することを困難にするツール。
これは強力なツールですが、そのレビューはこの記事の範囲外です。


2番目の方法(最初の方法と組み合わせることができます)は、 Kotlinコンパイラの機能を使用することです。 この機能については、次のセクションで説明します。


内部自動クラス


2つの方法の説明を変更して、この例を試してみましょう。


 inline fun <T> cAction(v : T, cb : T.() -> Int) = println(v.cb()) inline fun Action(cb : () -> Int) = println(cb()) 

この場合、関数の記述にinlineを追加しました。


プログラムをコンパイルした後、自動クラス用にコンパイラーによって作成されたファイルのリストは、「 KMainKt$Test$1.class 」、「 KMainKt$Test$2.class 」、「 KMainKt$Test$cb$1.class 」の3つに削減されました。 つまり 「Action」および「cAction」関数にパラメーターとして渡されないクラスのみがあります。 同時に、 KMainKt.classファイルのサイズは約200バイト増加しました。


残りのクラスはどこに行きましたか?


inline修飾子で宣言されたすべての関数のコードは、関数呼び出しの場所に直接挿入されました。 これに加えて、コンパイラーは同じ名前の別々の関数を作成しました。 言語の構文は、宣言された関数の使用を許可しません。 呼び出そうとすると、そのコードが使用場所に配置されますが、静的コピーが作成され、 reflection介してこの関数にアクセスできるようになります。


どのように機能しますか?


Kotlinの匿名コードのブロックは、使用場所に応じて、自動オブジェクトの助けを借りて実装するか、呼び出しサイトに直接配置できます。 最初のケースでは、特定のクラスのオブジェクトが作成され、その説明はこのセクションにあります。2番目のケースでは、オブジェクトなどのブロックはまったく存在しません。


自動クラスを作成する場合、Kotlinは2種類のオブジェクトを作成できます。


  1. あるクラスのオブジェクト、カスタムクラスの子孫。
  2. コードブロックを呼び出すための特別なクラスを表すオブジェクト。

カスタムタイプオブジェクト


最初のメソッドは、匿名クラス、ラムダ関数を作成し、デリゲートを使用してインターフェイスを実装するために使用されます。 この場合、指定された(明示的または暗黙的に)ユーザークラスの後継であるクラスが作成され、そのようなクラスのメソッドの1つにコードブロックが配置されます。


コード
 fun add(i : ActionListener) {} add(object : ActionListener { override fun actionPerformed(e : ActionEvent?) {} }) add(ActionListener { e -> }) 

匿名クラス用に自動クラスが作成された場合、そのクラスのオブジェクトは宣言の場所で構築され、パラメーターとして宛先に渡されます。 機能的インターフェースを実装するためにクラスが作成され、ローカル変数をキャプチャしない場合、このクラスの以前に作成されたシングルトンが使用されます。 ローカル変数がキャプチャされると、新しいオブジェクトが構築され、キャプチャされたすべての変数へのリンクとして、パラメータとしてコンストラクタに渡されます。


そのようなクラスを呼び出す実装とメソッドは次のようになります
 //   : add(ActionListener { e -> }) class AutoClassForCase1 : ActionListener { override fun actionPerformed(e : ActionEvent?) { //        - } companion object { @JvmField val INSTANCE = AutoClassForCase1() } } add( AutoClassForCase1.INSTANCE ) //     

ローカル変数のキャプチャ


Kotlinでは、 Javaとは異なり、匿名コードで任意のタイプのローカル変数をキャプチャできます( Javaeffectively final 」である必要があります)。したがって、キャプチャされた変数の自動クラスへの転送は特別なクラス「 Ref<T> 」を通じて行われます。 このアプローチの結果、ローカル変数が少なくとも1つのコードブロックによってキャプチャされた場合、使用する場所でリンクを自動的にラップし、このリンクを使用してすべての呼び出しが行われます。 このメソッドは、 Javaで使用されるメソッドと比較して追加のオーバーヘッドをもたらしますが、変数の値を変更できます。


ローカル変数キャプチャコード

ブロックがローカル変数をキャプチャすると、コードは次のようになります。


  fun Test() { val local = 10 Action{ local+1 } local + 2 } 

これになります:


  fun Test() { val local = Ref<Int>(10) //      Action{ local.value + 1 } //        local.value + 2 //     local.value = null //       } 

この場合、次の点に注意する必要があります。


  1. コードブロックからキャプチャされた変数の値は、関数本体のリンクと同じオブジェクトを含む自動クラスのオブジェクトを介してアクセスされます。 このオブジェクトは、コードブロックの宣言の場所で作成され、自動クラスオブジェクトのコンストラクターに渡されます。
  2. コードが現在のコンテキストから変数をキャプチャする場合、シングルトンが自動クラスのオブジェクトとして使用されるのではなく、新しいオブジェクトが作成されます。 これは、コンストラクターを呼び出して、キャプチャされた変数への参照を渡すために発生します。
  3. 関数の最後に、Kotlinはキャプチャされたすべての変数のオブジェクトクリーンアップコードを自動的に生成するため、関数の終了後、ローカルオブジェクトへのリンクの数は、作成された自動オブジェクトの数と常に等しくなります。

コードブロックのオブジェクト


匿名コードブロックを呼び出すための上記の例は、コンパイラによっておよそ次のように実装されます。


匿名コードブロックの実装
  //       class AutoClassForMethod : Function0<Int> { //       private val local : Ref<Int> //       constructor( l:Ref<Int> ) { local.value = l.value } //     fun invoke() : Int { //      } } //          Action( AutoClassForMethod(local) ) 

この例では、簡単にするために、 AnyKotlinコードのこの部分はJavaで記述されているため実際にはObject )から特定の型への中間パラメーター変換のレイヤーを省略しました。 これは理解に影響しません。


すべてのKotlinコードブロックは、常にFunctionX<Ret[,Param1[,...]]>タイプFunctionX<Ret[,Param1[,...]]>は、任意の数のパラメーターを使用してメソッド呼び出しを実装するテンプレートクラスのセットです。 テンプレートの最後のタイプは戻り値のタイプ、最初のタイプは最初のパラメーターのタイプなどです。 クラス名にを使用して、メソッドパラメーターの数を示しました。 つまり Kotlinで()->Unitとして説明されているメソッドは、実際の型Function0<Unit>を持ち、 (Int,String)->Double型のFunction2<Int,String,Double>などとして説明されています。


FunctionXテンプレートは、 Kotlinライブラリ " kotlin-runtime.jar "に記述されており、プログラムで使用できます。 ()->Unit形式で「標準」構文を使用し、 Function0<Unit>テンプレートを使用してコードブロックを記述する構文は、まったく同じです。 これを確認するには、元の例の関数宣言を置き換えます。


 fun <T> cAction(v : T, cb : Function1<T,Int>) = println(cb.invoke(v)) fun Action(cb : Function0<Int>) = println(cb.invoke()) 

プログラムは以前のようにアセンブルされ、実行されます。
以前に使用された構文「 T.() -> Int 」は、テンプレートの使用に完全に透過的に置き換えられ、メソッドが呼び出されるオブジェクトのタイプに対応する追加のタイプを渡すことに注意してください。 つまり 静的関数の呼び出しとオブジェクト関数の呼び出しの違いは、追加パラメーター「 this 」のみです。


これで何が便利ですか?


単純なユーザーの観点からは、利用可能な言語の構文はここではあまり役に立ちません。


ただし、ライブラリの開発者、または動的メソッドリストを使用して複雑な制御アルゴリズムを実装する人の観点からは、自動オブジェクトの配置方法とコードブロックの処理方法を知ることは非常に役立ちます。


— .



inline-


inline . .


inline , . つまり " ++ ", . inline- Kotlin - inline- " ++ ". , .


inline , , . , . つまり " inline " .


inline- inline-


, " inline " , . , " inline ", ..


: inline-
 class Holder { var cb : (()->Unit)? = null inline fun setup( cb:()->Unit ) { this.cb = cb // :    inline- } } fun Test() { Holder().setup{ } } 

inline - inline . , , - . , , inline-, , " noinline ".


noinline
 class Holder { var cb : (()->Unit)? = null inline fun setup( cb:()->Unit, noinline delayed:(()->Unit)?=null ) { this.cb = delayed cb() CallCodeWithDelay() } fun DelayPassed() = cb?.invoke() } 


++ inline . Kotlin . , , . , . " inline " .


, inline , . , , Kotlin , Lint , , inline . , , .


 @Suppress("NOTHING_TO_INLINE") inline fun SetupControl( ctl: Label ) { ctl.text = "Label" ctl.setSize( 100,100 ) } fun Test() { SetupControl( Label() ) } 

SetupControl "" , .



" ++ ", , , inline .


, Kotlin return . , inline , .


 fun Test1() : Int { Action { return 3 } } 

inline , , return , , . つまり , " Test ". return — .



, inline , , .


JVM , , . , , ..


:
 fun <T,P> templateTest(value : T) : P { if ( value is P ) // :    return value } templateTest<String,String>("text") 

inline- " reified ".


 inline fun <T,reified P> templateTest(value : T) : P { if ( value is P ) //         return value } 

. , templateTest , . , Kotlin reified ,
, , , refrection , , reified , .


 This function has a reified type parameter and thus can only be inlined at compilation time, not called directly 

inline-


, inline , .
Kotlin . . .



: ,


inline-
  • .. , .


  • , , . , , , «Kotlin», «Ref». , . , . inline- , ..


  • , , .


  • inline- .

inline-
  • , , "".


  • : , , ..


  • , . , .
    , .


  • JVM Java 7 Java 8 65534 (.. 65536 , ). inline- , . , .

, inline- . Kotlin , — .


, .
inline-, :


  • , , , , . -, .


  • , (.. -inline- ). inline- , , , , , . つまり , inline , — ..


  • . , .


  • , . — . .


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


All Articles