61.ボクシングされたプリミティブ型(boxed primitive)より、プリミティブ型(primitive)を選択すべし
ボクシングされたプリミティブ型とプリミティブ型の違い
Item6でも述べられれているように、オートボクシングとオートアンボクシングにより、ボクシングされたプリミティブ型(以下boxed primitive)とプリミティブ型(以下primitive)の違いはあいまいになっている。
しかし、これらの2つには違いがあり、使用時にはその差異に気を付けながら使わなければならない。その違いは3つで、
- primitiveは値のみを持つが、boxed primitiveは値とは異なる形で参照を持つ。
- primitiveは機能する値しか持ちえないが、boxed primitiveはnull(nonfunctional value)があり得る。
- primitiveのほうが、時間的にも省メモリスペース的にも優れている。
boxed primitiveを使用した場合の誤り例
primitiveは値のみを持つが、boxed primitiveは値とは異なる形で参照を持つ
以下のコードは一見うまく比較をしてくれるメソッドにみえる。
// Broken comparator - can you spot the flaw?
Comparator<Integer> naturalOrder =
(i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
しかし、このメソッドをnaturalOrder.compare(new Integer(42), new Integer(42))
のように用いると、想定としては等しいので0を返すはずだが、実際には1を返す。
1つ目の判定(i < j)は、auto-unboxedされて正しく動作する。
一方、2つ目の判定(i == j)は、同値であることをみる、つまり、参照が同じであるかをみている。よって、この比較はtrueにはならず、結果として1が返却される。 boxed primitiveに対して==演算子を用いることはたいてい間違っている。
上記の誤りを防ぐためには、Comparator.naturalOrder()
を用いるか、自身でcomparatorを書くのであれば、以下のようにprimitiveでの比較を行うようにする。
Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> {
int i = iBoxed, j = jBoxed; // Auto-unboxing
return i < j ? -1 : (i == j ? 0 : 1);
};
primitiveは機能する値しか持ちえないが、boxed primitiveはnull(nonfunctional value)があり得る
以下のプログラムでは、ヌルポが発生する。
package tryAny.effectiveJava;
public class BoxedPrimitive {
static Integer i;
public static void main(String[] args) {
if (i == 42)
System.out.println("Unbelievable");
}
}
i==47のところで、Integerとintを比較している。
boxed primitiveとprimitiveが混じった処理においては、たいていの場合boxed primitiveがauto-unboxされる。
nullを参照しているオブジェクトをunboxするとヌルポが生じるが、それがここでは起きている。
primitiveのほうが、時間的にも省メモリスペース的にも優れている
以下は、Item6で扱ったプログラムである。
package tryAny.effectiveJava;
public class BoxedPrimitive2 {
// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
}
box化、unbox化が繰り返し起こるため、これはローカル変数の型をlongにした場合よりも格段に遅い。
総括
3つの問題点があったが、最初の2つは結果がおかしくなり、最後の1つは性能が悪くなる。
boxed primitiveの使い時としては以下の場合がある。
- collectionの要素、キー、バリューとして使うとき。primitiveをcollectionに入れることはできないので使わざるを得ない。
- パラメータ化された型に用いるとき。例えば、
ThreadLocal<int>
という型は宣言できないが、ThreadLocal<Integer>
は宣言できる。 - リフレクションでメソッドを参照するときはboxed primitiveを使う(?)。(Item65)