Kotlinとオートボクシング

最近、Kotlinのトピックに関する多くのプレゼンテーションと記事があります。 同時に、それらのほとんどは、言語の主な利点のリストに要約されますが、コードの内部およびコードがどのバイトに変換されるかを考慮することはありません。



しかし、無駄に 最近、主な機能の概要でKotlinの記事の1つを読んだばかりの同僚の1人と話をして、彼はヌル安全が悪であり、例外処理によって実装されていることを私に証明しました。 コードの実行:



name?.length
      
      





コンパイラは、単に呼び出しをtry-catchにラップして、 NullPointerExceptionをキャッチしようとします。



同様に、次のレビュー後の別の同志は、varがJSのようにKotlineにあるので、タイピングはあちこちで動的であり、実際、「これらのvar / valはすべて悪で、何も明確ではない、Javaにあるのは良いことだ」いいえ。」 こんにちは、JEP286と言います!



言語の普及の別の失敗例は、最近Kotlinでのプレゼンテーションで、レポートの著者自身がJavaのプリミティブに関連付けられた言語の動作をまったく正しく説明せず、参照型が常にKotlinで使用されることを告げたときに起こりました。 これについてもっとお話ししたいと思います。



オートボクシング/アンボクシングの問題の本質はJavaで知られています :プリミティブ型があり、参照ラッパークラスがあります。 ジェネリック型を使用する場合、プリミティブを使用できません。 実行時のジェネリック自体は消去され(そう、リフレクションによってこの情報を取得できます)、それらの代わりに、通常のオブジェクトとコンパイラが追加する型のゴーストが存在します。 ただし、Javaでは、プリミティブ型から参照型へのキャスト、つまり intからjava.lang.Integerに、またはその逆に、それぞれオートボックス化およびアンボックス化と呼ばれます。 これに起因するすべての明らかな問題に加えて、1つのことに興味があります-そのような変換では、新しい参照オブジェクトが作成されますが、全体としてパフォーマンスにあまり影響しません(はい、実際、オブジェクトは常に作成されるわけではありませんが、キャッシュにヒットしません)。



それでは、Kotlinはどのように動作しますか?



まず、Kotlinにはkotlin.Intkotlin.Longなどの独自のタイプのセットがあることを思い出してください 。 そして、一見すると、この状況はJavaよりもさらに悪いように見えるかもしれません。 オブジェクトの作成は常に行われます。 しかし、これはそうではありません。 Kotlin標準ライブラリの基本クラスは仮想です。 つまり、クラス自体はコードを記述する段階でのみ存在し、コンパイラはそれらをプラットフォームのターゲットクラス、特にJVM kotlinに変換します。Intは intに変換されます。 つまり Kotlinのコード:



 val tmp = 3.0 println(tmp)
      
      





コンパイル後:



 double tmp = 3.0D; System.out.println(tmp);
      
      





NullタイプのKotlinは、参照タイプ、つまり kotlin.Int? -> java.lang.Integerは 、非常に論理的です。



 val tmp: Double? = 3.0 println(tmp)
      
      





コンパイル後:



 Double tmp = Double.valueOf(3.0D); System.out.println(tmp);
      
      





拡張メソッドとプロパティについても同様です。 null以外の型を指定すると、コンパイラはプリミティブをレシーバーに置き換え、 null可能な場合はラッパー参照クラスに置き換えます。



 fun Int.example() { println(this) }
      
      





コンパイル後:



 public final void example(int receiver) { System.out.println(receiver); }
      
      





一般に、主なアイデアは明確です。コンパイラは、可能であれば、Javaプリミティブを使用しようとしますが、他の場合は参照クラスを使用します。



これはすべて良いことですが、プリミティブの配列はどうですか?



ここで状況は似ています:プリミティブの配列には、Kotlinに類似物があります。たとえば、 IntArray-> int []などです。 他のすべてのタイプでは、一般化されたクラスArray-> T []が使用されます。 さらに、Kotlinの配列は、コレクションと同じ「機能」操作をすべてサポートしています。 mapfoldreduceなど。 繰り返しますが、ボンネットの下には、各操作に対して呼び出される一般化された関数があり、その結果、コードのバイトレベルで各反復で同じボクシングが機能すると想定できます。



 val intArr = intArrayOf(1, 2, 3) println(intArr.fold(0, { acc, cur -> acc + cur }))
      
      





ただし、このような操作ごとに、Kotlinには必要なタイプの適切なメソッドがあるため、これは発生しません。 配列のタイプのみが異なる多くの同様の関数が得られることは明らかですが、コード生成はこの問題を解決するために内部的に使用されます。 さらに、関数自体と送信されたラムダは呼び出しポイントでインラインになるため、上記のすべてのコードは単純なループで展開されます。



 int initial = 0; int accumulator = initial; for(int i = 0; i < receiver.length; ++i) { int element = receiver[i]; accumulator += element; } System.out.println(accumulator);
      
      





また、配列の多くの関数(たとえば、 map )は新しい配列ではなく、リストを返します。その結果、Javaで一般化されたコードの場合と同様に、オートボクシングは引き続き機能します。



多くの懐疑論者は、「これらすべての新しい言語」のパフォーマンスの問題を非常に心配しています。 上記のすべてから、(Kotlinによって生成され、Javaで手動で記述された結果コードがほぼ同一であるため、ベンチマークに頼ることなく)オートボックスイン/ボックス化解除に関連する例のパフォーマンスは少なくとも類似していると結論付けることができます。 ただし、他のツールやライブラリと同様に、Kotlinが内部で何が起こっているのかを理解する必要があるという事実をキャンセルする人はいません。



All Articles