Java自動装、抜箱解析

8292 ワード

前言
少し前には自動箱詰め、自動で箱を開けると聞いていましたが、箱を入れて、箱を開けるとは一体何なのかは分かりませんでした。
    public static void main(String[] args) {
        Integer i1 = 30;
        Integer i2 = 30;
        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i1 == i2);
        System.out.println(i3 == i4);
    }
    //    :
    //true
    //false
    //why?     ,    !
  • 基本概念
  • 自動箱詰め:8種類の基本データタイプがある条件で使用されると、自動的に対応するパッケージタイプになります。上のコードは自動箱の一種です。
  • 自動で箱を開けます。8種類の包装タイプはいくつかの条件で使うと、自動的に対応する基本データタイプになります。
  • 簡単に言えば、コード表現はこのようなものです。
  • 自動箱詰め:Integer i=10;(int->Integer)
  • 自動取外し:int n=i;Integer->int)
  • 付:8種類の基本データタイプ
    基本タイプ
    占有空間(Byte)
    範囲を表す
    包装の種類
    bollan
    1/8
    true/false
    ボロア
    char
    2
    -128~127
    Chracter
    byte
    1
    -128~127
    Bytte
    ショート?ト
    2
    -2^15~2^15-1
    Shot
    要点
    4
    -2^31~2^31-1
    インテグ
    long
    8
    -2^63~2^63-1
    Long
    float
    4
    -3.403 E 38~3.403 E 38
    Float
    ドビー
    8
    -1.798 E 308~1.798 E 308
    Double
    自動箱を認識する
    冒頭のコードを見て、初めてtrueを出力したのは道理にかなっていますが、2回目の出力はfalseです。これはとても疑問です。
        public static void main(String[] args) {
             Integer i1 = 30;
             Integer i2 = 30;
             Integer i3 = 128;
             Integer i4 = 128;
             System.out.println(i1 == i2);
             System.out.println(i3 == i4);
         }
         //    :
         //true
         //false
         //why?     ,    !
    
    解析:包装器類を「=」比較すると、内部からInteger.valueOfメソッドを呼び出して自動箱詰め(int->Integer)を行います。
      /**
         * Returns an {@code Integer} instance representing the specified
         * {@code int} value.  If a new {@code Integer} 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.
         *
         * This method will always cache values in the range -128 to 127,
         * inclusive, and may cache other values outside of this range.
         *
         * @param  i an {@code int} value.
         * @return an {@code Integer} instance representing {@code i}.
         * @since  1.5
         */
        public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
    
    ソースからは、Integerのオブジェクトの内部にはInteger Cacheクラスがあり、キャッシュ(-128127範囲の数値)が表示されます。超過したら、新しいIntegerクラスに戻ります。「=」の比較はメモリアドレスであるため、「-128127」の数値範囲で比較したのは同じオブジェクトであり、trueを取得し、その範囲を超えた場合は自動箱詰め後の新しいオブジェクトに戻るためfalseを得る。
    まとめ:
  • Integer、Shot、Byte、Charcter、Longのこれらの包装種類のvalueOf方法の実現は類似の
  • です。
  • Double、FloatのvalueOf方法の実現は同様の
  • である。
  • BooleanのvalueOf方法の実装は、return (b ? TRUE : FALSE);
  • のような3つの目の演算です。
    自動解凍
    もう一つのコードのデモを見て、自動的に箱を開けます。
        public static void main(String[] args)  {
            Integer i1 = 30;
            int i2 = 30;
            int i3 = 128;
            Integer i4 = 128;
            System.out.println(i1 == i2);
            System.out.println(i3 == i4);
        }
        //    :
        //true
        //true
    
    解析:基本タイプと「=」の比較をすると、包装器類はintValueメソッドを呼び出して自動的に箱を外す(Integer->int)
        /**
         * Returns the value of this {@code Integer} as an
         * {@code int}.
         */
        public int intValue() {
            return value;
        }
    
    ソースからは、直接に真実の値を返すことができますが、範囲の制限がないので、2回の出力はtrueです。
    箱詰めと箱開けはどうやって実現しますか?
    ここを見て、「自動箱詰めがvalueOfメソッドを呼び出しているということがどこで分かりますか?自動で箱を開けるとintValue方法が呼び出されますか?」答えは-classファイルの逆コンパイルによって、簡単なプレゼンテーションを行います。
    public class StudyJava{
        public static void main(String[] args){
            Integer i = 1;
            int i2 = i;
        }
    }
    
    コンパイル後、コンソールでjavap-c StudyJavaコマンドを使用して得ることができます。
    Compiled from "StudyJava.java"
    public class StudyJava {
      public StudyJava();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."":()V
           4: return
      public static void main(java.lang.String[]);
        Code:
           0: iconst_1
           1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
           4: astore_1
           5: aload_1
           6: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
           9: istore_2
          10: return
    }
    
    逆コンパイルから得られたバイトコードは、箱詰め時に確かにInteger.valueOfメソッドを呼び出しています。箱を外す時はInteger.intValueメソッドを呼び出します。
    注意すべき点
  • Double、FloatのvalueOf方法の実現は、Integer、Shot、Bytte、Charracter、Longといういくつかの種類の実現方法と異なる。理由:一定の範囲では整数値は限られていますが、浮動小数点に対しては違います。
  • /**
         * Returns a {@code Double} instance representing the specified
         * {@code double} value.
         * If a new {@code Double} instance is not required, this method
         * should generally be used in preference to the constructor
         * {@link #Double(double)}, as this method is likely to yield
         * significantly better space and time performance by caching
         * frequently requested values.
         *
         * @param  d a double value.
         * @return a {@code Double} instance representing {@code d}.
         * @since  1.5
         */
        public static Double valueOf(double d) {
            //     ,             
            return new Double(d);
        }
    
    ソースコードの実現が分かりました。下のコードの出力結果も分かりましたよね。
    public class StudyJava{
        public static void main(String[] args){
            Double i1 = 100.0;
            Double i2 = 100.0;
            Double i3 = 200.0;
            Double i4 = 200.0;
            System.out.println(i1==i2);
            System.out.println(i3==i4);
        }
    }
    //    
    //false
    //fasle
    
  • 次にBoolean類を提示します。箱入りvalueOf実現コードは以下の通りです。
    /**
         * Returns a {@code Boolean} instance representing the specified
         * {@code boolean} value.  If the specified {@code boolean} value
         * is {@code true}, this method returns {@code Boolean.TRUE};
         * if it is {@code false}, this method returns {@code Boolean.FALSE}.
         * If a new {@code Boolean} instance is not required, this method
         * should generally be used in preference to the constructor
         * {@link #Boolean(boolean)}, as this method is likely to yield
         * significantly better space and time performance.
         *
         * @param  b a boolean value.
         * @return a {@code Boolean} instance representing {@code b}.
         * @since  1.4
         */
        public static Boolean valueOf(boolean b) {
            //TRUE、FALSE            ,          
            // public static final Boolean TRUE = new Boolean(true);
            // public static final Boolean FALSE = new Boolean(false);
            return (b ? TRUE : FALSE);
        }
    
    戻ってきたのが静的メンバーである以上、値が同じなら同じオブジェクトなので、下記のコードはよく分かります。
    public class StudyJava{
        public static void main(String[] args){
            Boolean i1 = false;
            Boolean i2 = false;
            Boolean i3 = true;
            Boolean i4 = true;
            System.out.println(i1==i2);
            System.out.println(i3==i4);
        }
    }
    //    
    //true
    //true
    
  • はちょっと複雑ですか?
  •  public class StudyJava{
        public static void main(String[] args){
           Integer i1 = 1;
           Integer i2 = 2;
           Integer i3 = 200;
           int i4 = i1 + 1;
           int i5 = 200;
           Integer i6 = 200;
           Long l1 = 3L;
           Long l2 = 2L;
           System.out.println(i4 == i2); - true
           System.out.println(i2.equals(i4)); - true
           System.out.println(i3 == i5); - true
           System.out.println(i3.equals(i5)); - true
           System.out.println(i3 == i6); - false
           System.out.println(i3.equals(i6)); - true
           // System.out.println(l2==i2);//       ,      ,    
           System.out.println(l1 == (i1 + i2)); - true//           ,        
           System.out.println(l2.equals(i1 + i2)); - false
        }
    }
    //    :
    //true
    //true
    //true
    //true
    //false
    //true
    //true
    //false
    
    この中で注目すべきは、最後の2行のコードです。「l 1=(i 1+i 2)」には算術演算が含まれていて、自動で箱を外す(算術演算は自動的に箱を開ける必要があります。それぞれintValue方法を呼び出して基本的なタイプを得る)トリガとなり、自動で箱に入れて、最終的には数値比較を行いましたので、正常にコンパイルできます。一方、「l 2.equals(i 1+i 2)」は、先に「i 1+i 2」をトリガする自動取外し(算術演算は自動的に取り外す必要があり、それぞれintValueメソッドを呼び出して基本タイプを得る)であり、算術演算は数値を得てから、数値対応型の自動箱詰め(valueOf)を行い、Integerのインスタンスオブジェクトに得て、最後にequals比較を行う。
    おわりに
    原理と自動包装、箱を外すタイミングに加えて、少し細心で本知識点を把握することができます。多くの実験をして、自ら検証してみてください。きっともっと多くの収穫があります。