Javaプリミティブとオブジェクト

Javaプリミティブとオブジェクト

1. 概要

このチュートリアルでは、Javaプリミティブ型とそれに対応するラップを使用する場合の長所と短所を示します。

2. Java型システム

Javaには、intbooleanなどのプリミティブとInteger,Booleanなどの参照型で構成される2つの型システムがあります。 すべてのプリミティブ型は参照型に対応しています。

すべてのオブジェクトには、対応するプリミティブ型の単一の値が含まれます。 wrapper classes are immutable(オブジェクトが構築されると状態が変更されないようにするため)およびfinal(オブジェクトから継承できないようにするため)。

実際には、実際の型が宣言された型と異なる場合、Javaはプリミティブ型と参照型の間の変換を実行します。

Integer j = 1;          // autoboxing
int i = new Integer(1); // unboxing

プリミティブ型を参照型に変換するプロセスはオートボクシングと呼ばれ、反対のプロセスはアンボクシングと呼ばれます。

3. 長所と短所

使用するオブジェクトの決定は、達成しようとするアプリケーションのパフォーマンス、使用可能なメモリの量、使用可能なメモリの量、および処理するデフォルト値に基づきます。

これらのいずれにも直面しない場合は、知っておく価値はありますが、これらの考慮事項を無視する可能性があります。

3.1. 単一アイテムのメモリフットプリント

参考までに、primitive type variablesはメモリに次の影響を与えます。

  • ブール-1ビット

  • バイト– 8ビット

  • short、char – 16ビット

  • int、float – 32ビット

  • ロング、ダブル– 64ビット

実際には、これらの値は仮想マシンの実装によって異なる場合があります。 たとえば、OracleのVMでは、ブール型はint値0および1にマップされるため、ここで説明するように32ビットが必要です:Primitive Types and Values

これらのタイプの変数はスタック内に存在するため、高速でアクセスされます。 詳細については、Javaメモリモデルのtutorialをお勧めします。

参照型はオブジェクトであり、ヒープ上に存在し、アクセスが比較的遅くなります。 それらには、原始的な対応物に関する特定のオーバーヘッドがあります。

オーバーヘッドの具体的な値は、一般的にJVM固有です。 ここでは、これらのパラメーターを使用した64ビット仮想マシンの結果を示します。

java 10.0.1 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)

オブジェクトの内部構造を取得するには、Java Object Layoutツールを使用できます(オブジェクトのサイズを取得する方法については、別のtutorialを参照してください)。

このJVM上の参照型の単一インスタンスは、192ビットを占めるLongDoubleを除いて、128ビットを占めることがわかります。

  • ブール-128ビット

  • バイト-128ビット

  • Short、Character – 128ビット

  • 整数、浮動小数点数– 128ビット

  • ロング、ダブル– 192ビット

Booleanタイプの単一の変数が128個のプリミティブ変数と同じくらいのスペースを占めるのに対し、1つのInteger変数は4つのintタイプと同じくらいのスペースを占めることがわかります。

3.2. アレイのメモリフットプリント

検討中の型の配列を占めるメモリの量を比較すると、状況はさらに興味深いものになります。

タイプごとにさまざまな数の要素を持つ配列を作成すると、プロットが得られます。

image

これは、メモリm(s)が配列の要素数にどのように依存するかに関して、型が4つのファミリにグループ化されていることを示しています。

  • long、double:m(s)= 128 + 64 s

  • short、char:m(s)= 128 + 64 [s / 4]

  • バイト、ブール値:m(s)= 128 + 64 [s / 8]

  • 残り:m(s)= 128 + 64 [s / 2]

ここで、角括弧は標準の天井関数を示します。

驚いたことに、longおよびdoubleのプリミティブ型の配列は、ラッパークラスLongおよびDoubleよりも多くのメモリを消費します。

そのsingle-element arrays of primitive types are almost always more expensive (except for long and double) than the corresponding reference typeのいずれかを見ることができます。

3.3. パフォーマンス

Javaコードのパフォーマンスは非常に微妙な問題であり、コードを実行するハードウェア、特定の最適化を実行する可能性のあるコンパイラ、仮想マシンの状態、他のプロセスのアクティビティに大きく依存しますオペレーティング・システム。

すでに述べたように、プリミティブ型はスタックに存在し、参照型はヒープに存在します。 これは、オブジェクトへのアクセス速度を決定する支配的な要因です。

プリミティブ型の操作がラッパークラスの操作よりもどれだけ速いかを示すために、最後の要素を除いてすべての要素が等しい500万の要素配列を作成しましょう。次に、その要素のルックアップを実行します。

while (!pivot.equals(elements[index])) {
    index++;
}

配列にプリミティブ型の変数が含まれる場合と、参照型のオブジェクトが含まれる場合のこの操作のパフォーマンスを比較します。

よく知られているJMHベンチマークツールを使用しており(使用方法についてはtutorialを参照)、ルックアップ操作の結果を次のグラフにまとめることができます。

image

 

このような単純な操作でも、ラッパークラスの操作を実行するのに時間がかかることがわかります。

加算、乗算、除算などのより複雑な演算の場合、速度の差が急増する可能性があります。

3.4. デフォルト値

プリミティブ型のデフォルト値は0である(対応する表現では、i 数値型の場合は00.0dなど)、ブール型の場合はfalse、文字型の場合は。 ラッパークラスの場合、デフォルト値はnullです。

つまり、プリミティブ型はドメインからのみ値を取得できますが、参照型はある意味でドメインに属していない値(null)を取得する可能性があります。

変数を初期化しないままにしておくことは良い習慣とは見なされませんが、作成後に値を割り当てる場合があります。

そのような状況では、プリミティブ型変数の値がその型のデフォルト値と等しい場合、変数が実際に初期化されたかどうかを調べる必要があります。

null値は、変数が初期化されていないことを明確に示しているため、ラッパークラス変数にはそのような問題はありません。

4. 使用法

これまで見てきたように、プリミティブ型ははるかに高速で、必要なメモリもはるかに少なくて済みます。 したがって、それらを使用することをお勧めします。

一方、現在のJava言語仕様では、パラメータ化された型(ジェネリック)、Javaコレクション、またはReflectionAPIでのプリミティブ型の使用は許可されていません。

アプリケーションで多数の要素を含むコレクションが必要な場合は、上のプロットに示されているように、できるだけ「経済的な」タイプの配列を使用することを検討する必要があります。

5. 結論

このチュートリアルでは、Javaのオブジェクトが原始的な類似物よりも遅く、メモリへの影響が大きいことを示しました。

いつものように、コードスニペットはrepository on GitHub.にあります