Java の自動ボックス化とボックス化解除、およびその落とし穴に関する簡単な説明

高洛峰
リリース: 2017-01-16 15:51:27
オリジナル
1209 人が閲覧しました

この記事では、著者は Java の非常に重要で興味深い機能である自動ボックス化とアンボックス化を紹介し、ソース コードから自動ボックス化とアンボックス化の原理を解釈します。同時に、この機能は A を残します。トラップ。開発者は注意しないと簡単にこの罠に陥る可能性があります。

オートボクシング

定義

Java プログラムを書くとき、多くの場合、次の方法で Integer オブジェクトを定義します:

Integer i=100;

上記のコードから、ご存知のとおり、i は参照です。 Integer 型で、100 は Java のプリミティブ データ型です。基本データ型を対応するラッパー クラスに直接渡すこの方法は、オートボクシングと呼ばれます。

jdk 1.5 では、オートボクシングが初めて導入されました。 jdk 1.5 より前では、値 100 の Integer オブジェクトを定義したい場合は、これを行う必要があります:

Integer i=new Integer (100);

Principle

上記のコードは「Integer i=」です。 100; "にブレークポイントを置き、それを追跡します。

Java の自動ボックス化とボックス化解除、およびその落とし穴に関する簡単な説明

次に、プログラムが Integer クラスの valueOf(int i) メソッドにジャンプしていることがわかります

/**
   * Returns a <tt>Integer</tt> instance representing the specified
   * <tt>int</tt> value.
   * If a new <tt>Integer</tt> instance is not required, this method
   * should generally be used in preference to the constructor
   * {@link #Integer(int)}, as this method is likely to yield
   * significantly better space and time performance by caching
   * frequently requested values.
   *
   * @param i an <code>int</code> value.
   * @return a <tt>Integer</tt> instance representing <tt>i</tt>.
   * @since 1.5
   */
  public static Integer valueOf(int i) {
    if(i >= -128 && i <= IntegerCache.high)
      return IntegerCache.cache[i + 128];
    else
      return new Integer(i);
  }
ログイン後にコピー
ログイン後にコピー

つまり、ボクシングとは、jdk 自体が Integer.valueOf( 100) への呼び出しを完了することを意味します。 。

ボックス化解除

定義

Integer integer100=100;
int int100=integer100;
ログイン後にコピー

上記のコードから、integer100 は Integer 型の参照であり、int100 は int 型のプリミティブ データ型であることがわかります。ただし、Integer 型のオブジェクトを、対応するプリミティブ データ型の変数に割り当てることはできます。こちらは開封です。

開梱と梱包は逆の操作です。ボックス化は、対応するカプセル化クラスの変数にプリミティブ データ型を割り当てます。アンボックス化とは、パッケージ クラスの変数を、対応するプリミティブ データ型の変数に割り当てることです。梱包と開梱も、非常に適切な名前です。

原則

開封プロセス中に jdk が私たちのために何をしてくれたのかは誰もが推測していると思います。実験を通じて私たちの推測を証明してみましょう。

上記のコードの2行目にブレークポイントを置き、つまり「int int100=integer100;」にブレークポイントを置き、それを追跡します。

プログラムが Integer の intValue() メソッドにジャンプしていることがわかります。

/**
   * Returns the value of this <code>Integer</code> as an
   * <code>int</code>.
   */
  public int intValue() {
    return value;
  }
ログイン後にコピー

つまり、jdk は intValue() メソッドの呼び出しを完了するのに役立ちます。上記の実験では、integer100 の intValue() メソッドが呼び出され、その戻り値が int100 に割り当てられます。

拡張

実験1

Integer integer400=400;
int int400=400;
System.out.println(integer400==int400);
ログイン後にコピー

上記コードの3行目では、integer400とint400が==演算を行っています。これら 2 つは異なるタイプの変数です。 integer400 はボックス化されていませんか、それとも int400 はボックス化されていますか?手術の結果はどうなりましたか?

== 操作は、2 つのオブジェクトのアドレスが等しいかどうか、または 2 つの基本データ型の値が等しいかどうかを判断することです。したがって、integer400 がボックス化されていない場合、それは 2 つの基本型の値が比較され、この時点では等しいはずであり、int400 がボックス化されている場合、実行結果は true であると推測するのは簡単です。これは、2 つの値が比較されることを意味します。オブジェクトのアドレスが等しいかどうか、この時点ではアドレスは等しくなく、演算結果は false になります。 (なぜ作者が値 400 を割り当てたかについては、後で説明するトラップに関係します)。

実際の走行結果は真実です。ということで integer400 のアンボックス化です。コード追跡の結果もこれを証明しています。

実験2

Integer integer100=100;
int int100=100;
System.out.println(integer100.equals(int100));
ログイン後にコピー

上記のコードの3行目では、integer100のメソッドequalsのパラメータはint100です。 equals メソッドのパラメータは基本データ型ではなく Object であることがわかっているため、ここでは int100 ボックスにする必要があります。コード追跡の結果もこれを証明しています。

実際、メソッドのパラメータの型がプリミティブ データ型で、渡されたパラメータの型がそのカプセル化されたクラスである場合、それに応じて自動的にボックス化解除されます。メソッドのパラメータの型がカプセル化された型の場合、渡された If入力パラメータのタイプは元のデータタイプであるため、自動的にボックス化されます。

実験 3

Integer integer100 = 100;
int int100 = 100;
Long long200 = 200l;
System.out.println(integer100 + int100);
System.out.println(long200 == (integer100 + int100));
System.out.println(long200.equals(integer100 + int100));
ログイン後にコピー

最初の実験では、基本データ型がカプセル化されたクラスに対して == 演算を実行すると、カプセル化されたクラスがアンボックス化されることを学びました。 +、-、*、/ の場合はどうなるでしょうか?この実験でわかります。

+ 演算が実行されると、基本データ型はボックス化され、次のようになります:

• 4 行目で、integer100+int100 は、Integer 型および値 200 のオブジェクト o を取得し、このオブジェクト メソッドの toString() を実行します。そして出力 "200"

• 5 行目では、integer100+int100 は Integer 型で値 200 のオブジェクトを取得します。 == 演算はこのオブジェクトを long200 オブジェクトと比較します。明らかに false を出力します。 6 行目では、integer100+int100 は Integer 型で値 200 のオブジェクト o を取得します。 Long の等しいメソッドは、long200 と o を比較します。これは、両方とも異なるタイプのカプセル化クラスであるため、出力は false です。クラス、その後:

•第4行中,integer100+int100就会得到一个类型为int且value为200的基础数据类型b,再将b进行装箱得到o,执行这个对象的toString()方法,并输出”200”;

•第5行中,integer100+int100就会得到一个类型为int且value为200的基础数据类型b1,==运算将long200进行拆箱得到b2,显然b1==b2,输出true;

•第6行中,integer100+int100就会得到一个类型为int且value为200的基础数据类型b,Long的equals方法将b进行装箱,但装箱所得到的是类型为Integer的对象o,因为o与long200为不同的类型的对象,所以输出false;

程序运行的结果为:

200
true
false

因而,第二种推测是正确,即在+运算时,会将封装类进行拆箱。

陷阱

陷阱1

Integer integer100=null;
int int100=integer100;

这两行代码是完全合法的,完全能够通过编译的,但是在运行时,就会抛出空指针异常。其中,integer100为Integer类型的对象,它当然可以指向null。但在第二行时,就会对integer100进行拆箱,也就是对一个null对象执行intValue()方法,当然会抛出空指针异常。所以,有拆箱操作时一定要特别注意封装类对象是否为null。

陷阱2

Integer i1=100;
Integer i2=100;
Integer i3=300;
Integer i4=300;
System.out.println(i1==i2);
System.out.println(i3==i4);

因为i1、i2、i3、i4都是Integer类型的,所以我们想,运行结果应该都是false。但是,真实的运行结果为“System.out.println(i1==i2);”为 true,但是“System.out.println(i3==i4);”为false。也就意味着,i1与i2这两个Integer类型的引用指向了同一个对象,而i3与i4指向了不同的对象。为什么呢?不都是调用Integer.valueOf(int i)方法吗?

让我们再看看Integer.valueOf(int i)方法。

/**
   * Returns a <tt>Integer</tt> instance representing the specified
   * <tt>int</tt> value.
   * If a new <tt>Integer</tt> instance is not required, this method
   * should generally be used in preference to the constructor
   * {@link #Integer(int)}, as this method is likely to yield
   * significantly better space and time performance by caching
   * frequently requested values.
   *
   * @param i an <code>int</code> value.
   * @return a <tt>Integer</tt> instance representing <tt>i</tt>.
   * @since 1.5
   */
  public static Integer valueOf(int i) {
    if(i >= -128 && i <= IntegerCache.high)
      return IntegerCache.cache[i + 128];
    else
      return new Integer(i);
  }
ログイン後にコピー
ログイン後にコピー

我们可以看到当i>=-128且i<=IntegerCache.high时,直接返回IntegerCache.cache[i + 128]。其中,IntegerCache为Integer的内部静态类,其原码如下:

private static class IntegerCache {
    static final int high;
    static final Integer cache[];
 
    static {
      final int low = -128;
 
      // high value may be configured by property
      int h = 127;
      if (integerCacheHighPropValue != null) {
        // Use Long.decode here to avoid invoking methods that
        // require Integer&#39;s autoboxing cache to be initialized
        int i = Long.decode(integerCacheHighPropValue).intValue();
        i = Math.max(i, 127);
        // Maximum array size is Integer.MAX_VALUE
        h = Math.min(i, Integer.MAX_VALUE - -low);
      }
      high = h;
 
      cache = new Integer[(high - low) + 1];
      int j = low;
      for(int k = 0; k < cache.length; k++)
        cache[k] = new Integer(j++);
    }
 
    private IntegerCache() {}
  }
ログイン後にコピー

我们可以清楚地看到,IntegerCache有静态成员变量cache,为一个拥有256个元素的数组。在IntegerCache中也对cache进行了初始化,即第i个元素是值为i-128的Integer对象。而-128至127是最常用的Integer对象,这样的做法也在很大程度上提高了性能。也正因为如此,“Integeri1=100;Integer i2=100;”,i1与i2得到是相同的对象。

对比扩展中的第二个实验,我们得知,当封装类与基础类型进行==运行时,封装类会进行拆箱,拆箱结果与基础类型对比值;而两个封装类进行==运行时,与其它的对象进行==运行一样,对比两个对象的地址,也即判断是否两个引用是否指向同一个对象。

以上这篇Java の自動ボックス化とボックス化解除、およびその落とし穴に関する簡単な説明就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持PHP中文网。

更多Java の自動ボックス化とボックス化解除、およびその落とし穴に関する簡単な説明相关文章请关注PHP中文网!

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート