読者です 読者をやめる 読者になる 読者になる

【Effective Java】項目49:ボクシングされた基本データより基本データを選ぶ

Effective Java Java

『基本データ』と『ボクシングされた基本データ』

Java では int などの基本データ型(primitive type)と、String などの参照型(reference type)の2つの型があります。

int や double の基本データ型は、それぞれに対応するボクシングされた基本データ(boxed primitive)と呼ばれる参照型を持っています。

基本データ型 ボクシングされた基本データ
boolean Boolean
byte Byte
char Character
float Float
int Integer
long Long
short Short
double Double

『基本データ型』と『ボクシングされた基本データ』には次のような違いがあります。

  • 基本データ型は値。一方、ボクシングされた基本データはオブジェクト。
  • 基本データ型はどんな値も完全。一方、ボクシングされた基本データは null を持つ。
  • 基本データ型はボクシングされた基本データよりも時間的・空間的に効率的。

自動ボクシングと自動アンボクシング

Java 5 ではこれらを暗黙的に相互変換する『自動ボクシング』と『自動アンボクシング』という機能が追加されました。

基本データ型をボクシングされた基本データに変換するのが自動ボクシングです。 一方、ボクシングされた基本データを基本データ型に変換するのが自動アンボクシングです。

Integer i = 1; // 自動ボクシング
int n = i; // 自動アンボクシング

2つの違いを不明瞭にすることで便利になった反面、意図せずに様々な問題が発生する可能性があります。

ボクシングされた基本データ同士の比較

例えば二つの Integer を比較する Comparator を実装したと仮定します。

Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                return a < b ? -1 : (a == b ? 0 : 1);
            }
        };

中段にある2つの Integer を == で比較している箇所では注意が必要です。

Integer 同士の不等号は自動アンボクシングされるので、a < b は正しく評価されます。 一方、a と b の == による比較は自動アンボクシングされないので、単純にインスタンスの同一性の比較になります。

もし、同じ 1 を示す Integer であっても、Integer 同士のインスタンスが異なると、同一と判定されないので注意が必要です。

comparator.comare(new Integer(1), new Integer(1)); // => 1

一度、アンボクシングして int に変換する(Integer.intValue() を使う)などの必要があります。

Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                int x = a;
                int y = b; 
                return x < y ? -1 : (x == y ? 0 : 1);
            }
        };

null に対する自動ボクシング

自動ボクシングでは null に対するボクシングに注意する必要があります。

Integer i = null;
if (i == 42) { // => NullPointerException が発生
    System.out.println("true");
}

このように自動アンボクシングするような状況で、ボクシングされた基本データ型が null の場合には NullPointerException が発生します。

意図しない自動ボクシング

次のプログラムは非常に遅いプログラムです。

Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
    sum += i;
}
System.out.println(i);

これは、+= で加算する際に毎回 i を Long に自動ボクシングしているためです。 Integer.MAX_VALUE の回数だけインスタンスを生成するため非常に遅いプログラムになります。

使い分け

基本データ型が使えるところでは基本データ型を使うべきです。

ただし、次の場合は基本データ型は使えませんので、ボクシングされた基本データを使わなければなりません。

  • コレクションの要素として使う場合
  • リフレクションを使ってメソッドを呼び出す場合

それ以外はパフォーマンスや意味的な側面から、ボクシングされた基本データを使うことは推奨されません。

感想

数値リテラルが自動ボクシングされる場合、値が同じなら同一のインスタンスを参照するようになっているようだ。

Integer a = 1;
Integer b = 1;
a == b; // => 同じインスタンスを参照するので true になる

同じ中身の文字列リテラルはシステムで唯一のオブジェクトになる、というのと一緒だ。