オートボクシング

Revised: May/4th/2008

オートボクシングとアンボクシングは、J2SE 5.0 (Tiger)で追加された機能です。

ボクシング変換の概要

プリミティブ型とオブジェクト型を自動変換する仕様をオートボクシングと呼び、逆に、オブジェクト型からプリミティブ型に自動変換する機能をアンボクシングと呼びます。

ボクシング変換の対応
プリミティブ型ラッパークラス概要
booleanBooleanブーリアン(ブール代数)。truefalse
charCharacter16ビットUnicode UTF-16コードユニット(16ビット符号なし整数)
byteByte8ビット符号付整数(2の補数表現)
shortShort16ビット符号付整数(2の補数表現)
intInteger32ビット符号付整数(2の補数表現)
longLong64ビット符号付整数(2の補数表現)
floatFloat32ビットIEEE 754符号付浮動小数点数
doubleDouble64ビットIEEE 754符号付浮動小数点数

例えば、Integer型の場合は次の通りです。

// J2SE 1.4以下
Integer intObj = new Integer(127);
int i = intObj.intValue();

// J2SE 5.0以上
Integer intObj = 127;	// ボクシング
int i = intObj;	// アンボクシング

この機能は、java.util.Collectionクラスへの値の代入/取得で、Object型のみしか許されない場合などに威力を発揮します。従来は、Collectionクラスへ値を代入するときは、ラッパークラス型へ明示的に変換する必要がありました。逆に、ラッパークラスに対して算術演算子を作用させたい場合は、intValue()などのメソッドでプリミティブ型に変換する必要がありました。J2SE 5.0で導入された本仕様により、それらの変換は自動化されました。

次に例を挙げます:

class TestAutoboxing {
	public static void main(String[] args) {
		Double d = Double.valueOf(args[0]);	// インスタンス化
		while (true) {
			d *= 1.1;	// double型 -> Double型: アンボクシング
			if (d > 10000) break;	// Double型 -> double型: アンボクシング
		}
		System.out.println(d);
	}
}
D:\java>javac TestAutoboxing.java

D:\java>java TestAutoboxing 1024
10086.126260027011

D:\java>

この機能によって、算術演算子が作用する式中では、プリミティブ型とラッパークラス型が区別されなくなりました。非常に便利なのですが、equals()==については、他の参照型変数と同様の注意が必要です。ラッパークラス型はオブジェクトなので、メモリ上で別のインスタンスであればfalseが返ります。

拡張型変換

プリミティブ型の拡張型変換はサポートされますが、ラッパー型ではサポートされません。

次の例は、Integer型オブジェクトをdouble型に代入しています。これは正しいコードです。

class TestBoxingCast {
	public static void main(String[] args) {
		Integer i = 1024;
		double d = i;	// Integer -gt; int -> double
		System.out.println(d);
	}
}
C:\java>javac TestBoxingCast.java

C:\java>java TestBoxingCast
1024.0

C:\java>

一方、次のコードは正しくありません。Integer型をDouble型に代入しようとしていますが、コンパイル時に「互換性のない型」が報告されてエラーとなります。

class TestBoxingCast2 {
	public static void main(String[] args) {
		Integer i = 1024;
		Double d = i;	// Integer -> Double
		System.out.println(d);
	}
}
C:\java>javac TestBoxingCast2.java
TestBoxingCast2.java:4: 互換性のない型
検出値  : java.lang.Integer
期待値  : java.lang.Double
                Double d = i;   // Integer -> Double
                           ^
エラー 1 個

C:\java>

ラッパークラス間の拡張型変換はサポートされません。スーパークラスNumber型へ代入して、doubleValue()などのメソッドを利用することが考えられます。

オブジェクトの共用

ラッパー型はオブジェクトなので、equals()==は別の意味を持ちます。前者は等価であることが比較され、後者はメモリ上の実体が同じであるかが比較されます。算術演算子の作用によってボクシング変換が実施されますが、演算子==に対してはボクシング変換は自動化されていません。

valueOf()のすすめ

J2SE 5.0では、各ラッパークラスのメソッドvalueOf()が拡張されています。API仕様では、このメソッドが頻繁に要求される値をキャッシュするので、操作に必要な領域や時間がはるかに少なくて済む場合が多いとされています。つまり、valueOf()が実行されると、既に存在するオブジェクトが検索され、存在する場合はそのオブジェクトへの参照が返されることがあるということです。

class TestValueOf {
	public static void main(String[] args) {
		Integer i1 = 1024;
		Integer i2 = 1024;
		
		Double d1 = Double.valueOf(6.626 0693E-34);
		Double d2 = Double.valueOf(6.626 0693E-34);
		
		System.out.println("i1 == i2: " + (i1 == i2) + "\t: " + i1);
		System.out.println("d1 == d2: " + (d1 == d2) + "\t: " + d1);
	}
}

残念ながら、上記の実行結果はtureを返しませんでした。しかし、お作法として、コンストラクタよりもvalueOf()を優先的に使うべきでしょう。ラッパークラスのインスタンスは、String型と同様immutableであるので、同じ値を表すオブジェクトを複数生成しても、リソースの無駄です。

正規化されるリテラル

但し、ある値については、String型オブジェクトのように、別の場所で生成されたオブジェクトでも、==trueを返します。

internedなリテラル
符号付整数-128~127
文字\u0000~\u007f
ブーリアンtrue, false

つまり、byteの範囲内のリテラルは、オブジェクトがキャッシュされて再利用されるようです。

class TestImmutable {
	public static void main(String[] args) {
		Integer i1_1 = -128;	// internedな下限
		Integer i1_2 = -128;
		Integer i2_1 = 127;	// internedな上限
		Integer i2_2 = 127;

		Integer i3_1 = i2_1 + 1;	// internedでない
		Integer i3_2 = i2_1 + 1;
		Integer i4_1 = i1_1 -1;
		Integer i4_2 = i1_1 - 1;

		Character c1_1 = '\u0000';	// internedな下限
		Character c1_2 = '\u0000';
		Character c2_1 = '\u007f';	// internedな上限
		Character c2_2 = '\u007f';

		Character c3_1 = '\u0080';	// internedでない
		Character c3_2 = '\u0080';

		Boolean b1_1 = true;	// ブール代数はinterned
		Boolean b1_2 = true;
		Boolean b2_1 = false;
		Boolean b2_2 = false;

		// 比較文字列: 評価結果: 評価対象の値
		System.out.println("i1_1 == i1_2: " + (i1_1 == i1_2) + ": " + i1_1);
		System.out.println("i2_1 == i2_2: " + (i2_1 == i2_2) + ": " + i2_1);
		System.out.println("i3_1 == i3_2: " + (i3_1 == i3_2) + ": " + i3_1);
		System.out.println("i4_1 == i4_2: " + (i4_1 == i4_2) + ": " + i4_1);
		System.out.println("c1_1 == c1_2: " + (c1_1 == c1_2) + ": " + c1_1);
		System.out.println("c2_1 == c2_2: " + (c2_1 == c2_2) + ": " + c2_1);
		System.out.println("c3_1 == c3_2: " + (c3_1 == c3_2) + ": " + c3_1);
		System.out.println("b1_1 == b1_2: " + (b1_1 == b1_2) + ": " + b1_1);
		System.out.println("b2_1 == b2_2: " + (b2_1 == b2_2) + ": " + b2_1);
	}
}
D:\java>javac TestImmutable.java

D:\java>java TestImmutable
i1_1 == i1_2: true: -128
i2_1 == i2_2: true: 127
i3_1 == i3_2: false: 128
i4_1 == i4_2: false: -129
c1_1 == c1_2: true: 

D:\java>

JVMの実装に依存した非常にトリッキーな性質なので、この性質に頼ったコーディングはすべきではありません。コード範囲中でも、値によって評価結果が変わる旨を認識して、比較はequals()で行うようにお勧めします。



Copyright © 2008 SUGAI, Manabu. All Rights Reserved.