Java学習ノートの自動梱包と取り外し

9917 ワード

もっと多くの博文は私の個人の独立したブログを参考にすることができます:卑しい夢
自動梱包と解体とは
自動梱包とは、Javaがintの変数をIntegerオブジェクトに変換するなど、元のタイプ値を自動的に対応するオブジェクトに変換するプロセスを梱包と呼び、逆にIntegerオブジェクトをintタイプ値に変換するプロセスを解体と呼ぶ.自動梱包時にコンパイラはvalueOf()を呼び出して元のタイプ値をオブジェクトに変換する.自動的にボックスを取り外すと、コンパイラは、xxxValue()のような方法(intValue()doubleValue()など)を呼び出すことによって、オブジェクトを元のタイプの値に変換します.
元のタイプ(Primitive Type)
リファレンスタイプ(パッケージクラス)(Reference Type(Wrapper Class))
byte
Byte
short
Short
char
Character
int
Integer
long
Long
float
Float
double
Double
boolean
Boolean
どのような場合に自動梱包と解体が発生しますか?
付与時
Java 1.5の前に、手動でタイプ変換を行う必要がありましたが、現在ではすべての変換がコンパイラで完了しています.
//before autoboxing
Integer iObject = Integer.valueOf(3);
int iPrimitive = iObject.intValue()

//after java5
Integer iObject = 3;      //autobxing - primitive to wrapper conversion
int iPrimitive = iObject; //unboxing - object to primitive conversion

メソッド呼び出し時
メソッド呼び出しを行うと、元のデータ値またはオブジェクトが入力され、コンパイラも自動的に変換されます.
public static Integer show(Integer iParam){
   System.out.println("autoboxing example - method invocation i: " + iParam);
   return iParam;
}

//autoboxing and unboxing in method invocation
show(3); //autoboxing
int result = show(3); //unboxing because return type of method is Integer

自動梱包によるパフォーマンスの問題
「文字を1文字修正すれば、次のコードの実行速度が5倍になる」と言われたら、可能だと思いますか?
long t = System.currentTimeMillis();
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
    sum += i;
}
System.out.println("total:" + sum);
System.out.println("processing time: " + (System.currentTimeMillis() - t) + " ms");

出力結果:
total:2305843005992468481
processing time: 63556 ms
Longlongに変更し、運転結果をもう一度見てみましょう
long t = System.currentTimeMillis();
long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
    sum += i;
}
System.out.println("total:" + sum);
System.out.println("processing time: " + (System.currentTimeMillis() - t) + " ms");

出力結果:
total:2305843005992468481
processing time: 12229 ms

文字を1文字修正しただけで、パフォーマンスが2倍以上向上したことが実証されています.では、いったい何が原因なのでしょうか.なぜなら、+というオペレータはIntegerオブジェクトには適用されず、数値加算操作を行う前に自動解体操作が発生し、intに変換され、加算後に自動解体操作が発生し、Integerオブジェクトに置き換えられるからである.内部の変化は次のとおりです.
sum = sum.longValue() + i;
Long sum = new Long(sum);

明らかに、上記のサイクルでは2147483647個の「Long」タイプのインスタンスが作成され、このような膨大なサイクルでは、プログラムのパフォーマンスが低下し、ゴミ回収の作業量が増大する.
説明:パッケージに含まれる内容は変更されません.すなわち、Longオブジェクトは可変です.
リロードと自動梱包
Java 5以前では、value(int)value(Integer)は全く異なる方法であり、開発者は、伝来がintなのかIntegerなのか、どちらの方法を呼び出しているのか迷うことはありませんが、自動梱包と解体の導入により、リロード方法を処理する際にどのような変化があるのでしょうか.次の例で検討してみましょう.
public void test(int num){
    System.out.println("method with primitive argument");
}
public void test(Integer num){
    System.out.println("method with wrapper argument");
}
//calling overloaded method
AutoboxingTest autoTest = new AutoboxingTest();
int value = 3;
autoTest.test(value);  //no autoboxing 
Integer iValue = value;
autoTest.test(iValue); //no autoboxing

/** Output:
method with primitive argument
method with wrapper argument
*/

出力結果から、リロードの場合、自動箱詰め操作は発生しないことが分かる.(リロードと書き換えに関する基礎知識はJava学習ノートの書き換え(Overriding)とリロード(Overloading)を参照)
Javaでの自動梱包と解体の使用に注意すべき問題
自動梱包と解体はプログラミングの過程で私たちに大きな便利さをもたらしたが、間違いやすい問題もある.
オブジェクトの同等比較"=="は、元の値の比較にも、オブジェクト間の比較にも使用できます.オブジェクト間の比較を行う場合、比較対象が表す値ではなく、オブジェクトの参照が等しいかどうかが実質的に比較されます.オブジェクトの値を比較するには、オブジェクトに対応するequalsメソッドを使用します.以下の例で検討できます.
public class AutoboxingTest {
    public static void main(String args[]) {
        // Example 1: == comparison pure primitive – no autoboxing
        int i1 = 1;
        int i2 = 1;
        System.out.println("i1==i2 : " + (i1 == i2)); // true

        // Example 2: equality operator mixing object and primitive
        Integer num1 = 1; // autoboxing
        int num2 = 1;
        System.out.println("num1 == num2 : " + (num1 == num2)); // true

        // Example 3: special case - arises due to autoboxing in Java
        Integer obj1 = 1; // autoboxing will call Integer.valueOf()
        Integer obj2 = 1; // same call to Integer.valueOf() will return same cached Object
        System.out.println("obj1 == obj2 : " + (obj1 == obj2)); // true

        // Example 4: equality operator - pure object comparison
        Integer one = new Integer(1); // no autoboxing
        Integer anotherOne = new Integer(1);
        System.out.println("one == anotherOne : " + (one == anotherOne)); // false
    }
}

/** Output:
i1==i2 : true
num1 == num2 : true
obj1 == obj2 : true
one == anotherOne : false
*/

注目すべきは、Example 2では、比較が1つのオブジェクトと1つの元の値であり、このような状況が発生した場合の比較はオブジェクトの値であるべきである.困惑するExample 3は、最初に"=="がオブジェクト間の比較に用いられた場合、比較がそれらの引用であると述べたが、なぜobj1 == obj2が戻った結果がtrueなのか.これは極端である.メモリの節約を考慮すると、JVM-128から127までのIntegerオブジェクトをキャッシュします.つまり、obj1オブジェクトを作成すると、自動箱詰め操作が行われ、そのオブジェクトがキャッシュに保存されます.obj2オブジェクトを作成すると、同様に自動箱詰め操作が行われ、キャッシュに同じ値のオブジェクトがあるかどうかが検索されます.結果として、obj2のオブジェクトはobj1のオブジェクトを指します.obj1obj2は実際には同じオブジェクトです.したがって、"=="を使用して比較するとtrueに戻ります.Example 4は、コンストラクタを使用してオブジェクトを作成し、自動箱詰め操作が発生せず、キャッシュポリシーが実行されないため、oneanotherOneは異なる参照を指しますのです.
説明:このIntegerキャッシュポリシーは自動箱詰め(autoboxing)の場合にのみ使用され、コンストラクタを使用して作成されたIntegerオブジェクトはキャッシュできません.
混乱しやすいオブジェクトと元のデータ値
1つの間違いを犯しやすい問題は、オブジェクトと元のデータ値の違いを無視し、比較操作を行う場合、オブジェクトが初期化されていないかnullで、自動解体中にobj.xxxValueであれば、NullPointerExceptionが投げ出され、以下の例で検討することができる.
private static Integer count;
//NullPointerException on unboxing
if( count <= 0){
  System.out.println("Count is not started yet");
}

不要なオブジェクトを生成しGC圧力を増加
自動梱包では暗黙的にオブジェクトが作成されるため、前述したように、1つの循環体では不要な中間オブジェクトが作成され、GC圧力が増加し、プログラムの性能が低下するため、循環を書く際にコードに注意し、不要な自動梱包操作を導入しないようにしなければならない.
参考資料:1.What is Autoboxing and Unboxing in Java 2.Java Integer Cache