Java 5以降、プリミティブ型のボックス化/ボックス化解除が行われたため、int
はJava.lang.Integer
などにラップされます。
最近、多くの新しいJavaプロジェクトが見られます(definitely6ではない場合、少なくともバージョン5のJREが必要です) Java.lang.Integer
ではなくint
を使用していますが、long
値などに変換するためのヘルパーメソッドがいくつかあるため、後者を使用する方がはるかに便利です。
なぜstillがJavaでプリミティブ型を使用するのですか?具体的なメリットはありますか?
Joshua BlochのEffective Java、項目5:「不要なオブジェクトの作成を避ける」では、次のコード例を投稿しています。
public static void main(String[] args) {
Long sum = 0L; // uses Long, not long
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
実行には43秒かかります。 Longをプリミティブに取り込むと、6.8秒になります。それがプリミティブを使用する理由である場合。
ネイティブ値の等価性の欠如も懸念事項です(.equals()
は==
と比較してかなり冗長です)
biziclopの場合:
class Biziclop {
public static void main(String[] args) {
System.out.println(new Integer(5) == new Integer(5));
System.out.println(new Integer(500) == new Integer(500));
System.out.println(Integer.valueOf(5) == Integer.valueOf(5));
System.out.println(Integer.valueOf(500) == Integer.valueOf(500));
}
}
結果:
false
false
true
false
編集 (3)true
を返し、(4)false
?を返す理由
それらは2つの異なるオブジェクトだからです。ゼロに最も近い256個の整数[-128; 127]はJVMによってキャッシュされるため、それらに対して同じオブジェクトを返します。ただし、その範囲を超えると、キャッシュされないため、新しいオブジェクトが作成されます。事態をより複雑にするために、JLSは少なくとも256個のフライウェイトをキャッシュすることを要求しています。 JVM実装者は、必要に応じてさらに追加することができます。これは、最も近い1024がキャッシュされ、すべてがtrueを返すシステムで実行できることを意味します... #awkward
自動ボックス化は、NPEを見つけにくい場合があります
Integer in = null;
...
...
int i = in; // NPE at runtime
ほとんどの場合、in
へのnullの割り当ては、上記よりはるかに明白ではありません。
ボックス型はパフォーマンスが低く、より多くのメモリを必要とします。
プリミティブ型:
int x = 1000;
int y = 1000;
次に評価します:
x == y
true
です。驚くことではありません。ボックス化されたタイプを試してください:
Integer x = 1000;
Integer y = 1000;
次に評価します:
x == y
false
です。恐らく。ランタイムに依存します。その理由は十分ですか?
パフォーマンスとメモリの問題に加えて、別の問題を考えたいと思います: List
interface はint
なしで壊れます。
問題はオーバーロードされたremove()
メソッドです( remove(int)
vs. remove(Object)
)。 remove(Integer)
は常に後者の呼び出しに解決されるため、インデックスで要素を削除できませんでした。
一方、int
を追加および削除しようとすると、落とし穴があります。
final int i = 42;
final List<Integer> list = new ArrayList<Integer>();
list.add(i); // add(Object)
list.remove(i); // remove(int) - Ouch!
本当に想像できますか
for (int i=0; i<10000; i++) {
do something
}
代わりにJava.lang.Integerでループしますか? Java.lang.Integerは不変であるため、ループをインクリメントするたびに、単一のJVM命令でスタックのintをインクリメントするのではなく、ヒープに新しいJavaオブジェクトを作成します。パフォーマンスは悪魔的です。
IntよりもJava.lang.Integerを使用する方が便利なモードであることに本当に反対します。それどころか。オートボクシングとは、整数を使用せざるを得ない場所でintを使用できることを意味し、Javaコンパイラーがコードを挿入して新しい整数オブジェクトを作成します。オートボクシングとは、コンパイラが関連するオブジェクト構造を挿入することにより、整数が期待される場所でintを使用できるようにすることです。そもそもintの必要性を取り除くことも減らすこともありません。オートボクシングを使用すると、両方の長所を活用できます。ヒープベースのJavaオブジェクトが必要な場合は、整数が自動的に作成され、算術計算とローカル計算を行うだけでintの速度と効率が得られます。
プリミティブ型はmuch fast:
int i;
i++;
整数(すべての数字と文字列)は、不変タイプです。作成後は変更できません。 i
がIntegerの場合、i++
は新しいIntegerオブジェクトを作成します-メモリとプロセッサの点ではるかに高価です。
何よりもまず、習慣。 Javaで8年間コーディングした場合、かなりの慣性が蓄積されます。なぜそうする理由がないのに変更するのですか?ボックス化されたプリミティブを使用することで追加の利点が得られるというわけではありません。
もう1つの理由は、null
が有効なオプションではないことをアサートすることです。 2つの数値の合計またはループ変数をInteger
として宣言するのは無意味であり、誤解を招く可能性があります。
パフォーマンスの面もありますが、多くの場合、パフォーマンスの違いは重要ではありませんが(そうである場合はかなり悪いです)、誰もが既により速く簡単に書くことができるコードを書くことを好みません慣れている。
ちなみに、Smalltalkにはオブジェクト(プリミティブなし)しかありませんが、ヒープスペースを割り当てず、単純に特別なビットパターンを使用するために、小さな整数(32ビットすべてではなく、27ビットのみなど)を最適化しました。また、他の一般的なオブジェクト(true、false、null)には特別なビットパターンがあります。
そのため、少なくとも64ビットJVM(64ビットポインター名前空間)では、整数、文字、バイト、ショート、ブール、フロート(および小さなロング)のオブジェクトを(作成されたものを除く)明示的なnew ...()
)、特別なビットパターンのみで、通常の演算子で非常に効率的に操作できます。
私が最も重要な理由であると思うことを誰も言及していないとは信じられません。「int」がそうであるため、「Integer」よりも入力がはるかに簡単です。人々は簡潔な構文の重要性を過小評価していると思います。数値を使用しているほとんどの場合、ループインデックスに含まれているため、パフォーマンスは実際にそれらを回避する理由ではありません。これらのインクリメントと比較は、どんな単純なループでも(intまたはIntegerを使用しているかどうか).
もう1つの理由は、NPEを取得できることですが、ボックス化された型では非常に簡単に回避できます(また、常にNull以外の値に初期化する限り、回避することが保証されます)。
もう1つの理由は、(new Long(1000))==(new Long(1000))はfalseですが、それは ".equals"にはボックス型の構文サポートがない(演算子<、>と異なり) 、=、など)、「単純な構文」の理由に戻ります。
Steve Yeggeの非プリミティブループの例は、私のポイントを非常によく示していると思います。 http://sites.google.com/site/steveyegge2/language-trickery-and-ejb
これについて考えてください:インターフェースを使用してそれらをシミュレートする必要のあるJavaと比較して、関数型を適切な構文を持つ言語(関数型言語、Python、Ruby、さらにはCなど)で使用する頻度RunnableおよびCallableおよび名前のないクラスなど。
プリミティブを削除しないいくつかの理由:
削除された場合、古いプログラムは実行されません。
この新しいことをサポートするには、JVM全体を書き直す必要があります。
より多くのメモリを使用する値と参照を保存する必要があります。巨大なバイト配列がある場合、byte
を使用することは、Byte
を使用するよりも大幅に小さくなります。
int i
を宣言してからi
で処理を行うと問題は発生しませんが、Integer i
を宣言してから同じ処理を行うとNPEになります。
次のコードを検討してください。
Integer i1 = 5;
Integer i2 = 5;
i1 == i2; // Currently would be false.
間違っているでしょう。演算子はオーバーロードする必要があり、その結果、内容が大幅に書き換えられます。
オブジェクトラッパーは、対応するプリミティブよりも大幅に低速です。
オブジェクトはプリミティブ型よりもはるかに重いため、プリミティブ型はラッパークラスのインスタンスよりもはるかに効率的です。
プリミティブ型は非常に単純です。たとえば、intは32ビットで、メモリ内で正確に32ビットを占有し、直接操作できます。整数オブジェクトは完全なオブジェクトであり、(他のオブジェクトと同様に)ヒープに格納する必要があり、参照(ポインター)を介してのみアクセスできます。また、32ビット(4バイト)を超えるメモリを使用する可能性があります。
そうは言っても、Javaがプリミティブ型と非プリミティブ型を区別しているという事実は、プログラミング言語Javaの時代の兆候でもあります。新しいプログラミング言語にはこの区別がありません。そのような言語のコンパイラーは、単純な値またはより複雑なオブジェクトを使用している場合、それ自体で把握できるほどスマートです。
たとえば、Scalaにはプリミティブ型はありません。整数のクラスIntがあり、Intは実際のオブジェクトです(メソッドなどで使用できます)。コンパイラがコードをコンパイルするとき、背後でプリミティブなintを使用するため、Intの使用はJavaでプリミティブなintを使用するのと同じくらい効率的です。
他の人が言ったことに加えて、プリミティブなローカル変数はヒープからではなく、スタックから割り当てられます。ただし、オブジェクトはヒープから割り当てられるため、ガベージコレクションが必要です。
プリミティブ型には多くの利点があります。
int loops = 100000000;
long start = System.currentTimeMillis();
for (Long l = new Long(0); l<loops;l++) {
//System.out.println("Long: "+l);
}
System.out.println("Milliseconds taken to loop '"+loops+"' times around Long: "+ (System.currentTimeMillis()- start));
start = System.currentTimeMillis();
for (long l = 0; l<loops;l++) {
//System.out.println("long: "+l);
}
System.out.println("Milliseconds taken to loop '"+loops+"' times around long: "+ (System.currentTimeMillis()- start));
Longで「100000000」回ループするのにかかるミリ秒:468
「100000000」回ループするのにかかるミリ秒:31
ちなみに、このようなものがJavaに組み込まれているのを見るのは気にしないでしょう。
Integer loop1 = new Integer(0);
for (loop1.lessThan(1000)) {
...
}
Forループがloop1を0から1000に自動的に増分する場合、または
Integer loop1 = new Integer(1000);
for (loop1.greaterThan(0)) {
...
}
Forループがloop1 1000を0に自動的にデクリメントする場所。
内部でどのような最適化が行われているかを知るのは困難です。
ローカルで使用する場合、コンパイラーがヌル値の可能性を除外して最適化を行うのに十分な情報を持っている場合、パフォーマンスが同じまたは類似することを期待します。
ただし、プリミティブの配列は、明らかに 非常に異なる ボックス化されたプリミティブのコレクションからのものです。これは、コレクションの奥深くで可能な最適化が非常に少ないことを考えると理にかなっています。
さらに、Integer
は、int
と比較して、はるかに高い論理オーバーヘッドを持っています。今、あなたは、 int a = b + c;
は例外をスローします。
可能な限りプリミティブを使用し、ファクトリメソッドとオートボクシングに依存して、必要に応じてより意味的に強力なボックス化された型を提供します。
以前の回答に同意します。プリミティブラッパーオブジェクトを使用するとコストが高くなる可能性があります。ただし、アプリケーションのパフォーマンスが重要でない場合は、オブジェクトの使用時にオーバーフローを回避できます。例えば:
long bigNumber = Integer.MAX_VALUE + 2;
bigNumber
の値は-2147483647であり、2147483649になると予想されます。これは、コードのバグであり、以下を実行することで修正されます。
long bigNumber = Integer.MAX_VALUE + 2l; // note that '2' is a long now (it is '2L').
また、bigNumber
は2147483649になります。この種のバグは見逃されやすい場合があり、未知の動作や脆弱性につながる可能性があります( CWE-19 を参照)。
ラッパーオブジェクトを使用すると、同等のコードはコンパイルされません。
Long bigNumber = Integer.MAX_VALUE + 2; // Not compiling
そのため、プリミティブラッパーオブジェクトを使用すると、この種の問題を簡単に阻止できます。
あなたの質問はすでに答えられているので、これまで言及されていない情報をもう少し追加するために返信します。
クラス/オブジェクト型が必要な理由を尋ねる必要があります
オブジェクト型を使用する理由は、コレクションを扱うときに生活を楽にするためです。プリミティブをリスト/マップに直接追加することはできません。むしろ、ラッパークラスを記述する必要があります。 Readymade Integerの種類のクラスはここで役立ち、さらにInteger.pareseInt(str)のような多くのユーティリティメソッドがあります。
プリミティブ型ははるかに高速であり、多くを必要としますメモリ不足。したがって、それらを使用することをお勧めします。
一方、現在のJava言語仕様では、JavaコレクションまたはReflection APIで、パラメーター化された型(ジェネリック)のプリミティブ型を使用できません。
アプリケーションが多数の要素を持つコレクションを必要とする場合、上のプロットに示されているように、できるだけ「経済的な」タイプの配列の使用を検討する必要があります。
*詳細については、ソースを参照してください: https://www.baeldung.com/Java-primitives-vs-objects
Javaはプリミティブ型ですべての数学演算を実行するためです。この例を考えてみましょう:
public static int sumEven(List<Integer> li) {
int sum = 0;
for (Integer i: li)
if (i % 2 == 0)
sum += i;
return sum;
}
ここで、リマインダーと単項プラス演算はInteger(Reference)型には適用できません。コンパイラーはボックス化解除を実行し、演算を実行します。
したがって、Javaプログラムでオートボックス化およびボックス化解除の操作が何回発生するかを確認してください。なぜなら、この操作を実行するには時間がかかります。
一般に、参照型の引数とプリミティブ型の結果を保持することをお勧めします。