自動ボクシングとアンボクシングの原理

インタビューで自動ボクシングと自動ボクシングとは何かとよく聞かれますが、本日はコードによる自動ボクシングの原理を説明します。

Javaを研究したことのある人なら誰でも、オートボクシングが基本的なデータ型を整数、文字、長整数、倍精度浮動小数点数、ブール値などのラッパー型に自動的に変換することであることを知っています。次のコード例で説明します。

この例を最初に見てください。

public class Main {

    public static void main(String[] args) throws IOException {
        
        Integer a = 10;
        Integer b = 10;
        Integer c = 200;
        Integer d = 200;

        System.out.println(a == b);
        System.out.println(c == d);

    }
}
复制代码

結果は次のように出力されます。

画像-20220327183328788

1つは真で、もう1つは偽です、それは奇妙ではありませんか?明らかに、2つのオブジェクトは同じ値を参照していますが、最終結果が異なるのはなぜですか?

ここでは自動ボクシングの原理を紹介します〜

これらのコードを逆コンパイルすると、コンパイラが最下層のIntegerクラスのvalueOf()メソッドを自動的に呼び出すことがわかります。それでは、valueOf()メソッドとは何でしょうか。入ってソースコードを見てみましょう:

@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
    return i >= -128 && i <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[i + 128] : new Integer(i);
}
复制代码

このコードはどういう意味ですか?

簡単に言うと、つまり、実際にはIntegerクラス自体にキャッシュメカニズムがあり、それ自体が定数であり、Javaメソッド領域に存在します。指定された整数が[-128、127]の間にある場合、次のようになります。対応するIntegerクラスの参照(またはアドレス)から直接キャッシュに返されます。整数のサイズがこの範囲内にない場合、valueof()メソッドは新しいInteger型オブジェクトを作成して返します。

したがって、200は127より大きいので、返されるパッケージタイプのアドレスは毎回異なり、結果の理由を分析しました〜

同様に、Double型とブール型のオートボクシングの背後にある理論的根拠を見てみましょう。

同様の例を使用します。

public class Main {

    public static void main(String[] args) throws IOException {

        Double a = 1.0;
        Double b = 1.0;
        Double c = 2.0;
        Double d = 2.0;

        System.out.println(a == b);
        System.out.println(c == d);


    }
}
复制代码

実行後、次のことがわかります。

どちらもfalseを返し、Doubleのソースコードにも移動します。

@HotSpotIntrinsicCandidate
public static Double valueOf(double d) {
    return new Double(d);
}
复制代码

valueofが新しいラッパークラスオブジェクトを作成するたびに、オブジェクトの参照アドレスが新しくなるたびに、2つを比較すると、それらは異なることがわかります。

次はブール型です:

public class Main {

    public static void main(String[] args) throws IOException {

        Boolean a = true;
        Boolean b = true;
        Boolean c = false;
        Boolean d = false;

        System.out.println(a == b);
        System.out.println(c == d);

    }
}
复制代码

さらに苦労せずに:結果

画像-20220327191511471

ブール型のパッケージ化タイプを使用すると、返される結果はすべてtrueであることがわかります。慣例として、valueofメソッドを確認してください。

@HotSpotIntrinsicCandidate
public static Boolean valueOf(boolean b) {
    return b ? TRUE : FALSE;
}
复制代码

ソースコードで2つの変数TRUEとFALSEが見つかりましたが、これら2つは何ですか?見上げる

    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);
复制代码

原来是两个final关键字修饰的静态变量,这样说的话,那么这两个变量的地址都是不变的,也就是说,我们valueof里面return的对象的引用都是相同的~

剩下的Character、Short、Long、Byte基本上和Integer的valueof是类似的,Double、Float的是类似的,Boolean的valueof是单独一组的

以上就是自动装箱的原理了~

接下来我们就讲讲什么是自动拆箱,同样用代码来做讲解,看以下的示例:

public class Main {

    public static void main(String[] args) throws IOException {
        
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Long g = 3L;
        int int1 = 12;
        int int2 = 12;
        Integer integer1 = new Integer(12);
        Integer integer2 = new Integer(12);
        Integer integer3 = new Integer(1);
        System.out.println("c==(a+b)"+(c == (a+b)));
        System.out.println("g==(a+b)"+(g == (a+b)));
        System.out.println("c.equals(a+b)"+c.equals(a+b));
        System.out.println("g.equals(a+b)"+g.equals(a+b));
        System.out.println("int1 == int2"+(int1 == int2));
        System.out.println("int1 == integer1"+(int1 == integer1));
        System.out.println("integer1 == integer2"+(integer1 == integer2));
        System.out.println("integer3 == a"+(integer3 == a));
    }
}
复制代码

在这里我们先铺垫一下==和equals的区别

对于==来说,又分为了基本数据类型和引用数据类型(包括包装类)还有拆箱(这个等会说)

  • 基本数据类型:比较的是值是否形同

  • 引用数据类型:比较的是引用是否相同

对于equals来说,也有两种情况,一种是默认情况,还有一种是重写的情况

  • 默认情况:比较的是引用是否相同

  • 重写情况:有些类在内部对Object类的equals方法进行了重写,比如String类和Integer类,都对该方法进行了重写,因此这种情况下比较的就是值是否相同

我们进到Integer的equals看一看:

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return this.value == (Integer)obj;
    } else {
        return false;
    }
}
复制代码

会发现内部使用的是用==进行比较,这里==号两边一个是基本数据类型,一个是Integer包装类型,因此会进行一个自动拆箱的过程,编译器会调用Integer的intValue()方法,然后比较两个的值,所以这里的equals比较的就是值

其余和Integer同类型的包装类内部有各自对equals方法的重写方式,例如Long:

public boolean equals(Object obj) {
   if (obj instanceof Long) {
       return this.value == (Long)obj;
   } else {
       return false;
   }
}
复制代码

铺垫完了==和equals的不同,我们就可以继续来分析自动拆箱了~

  1. c == (a+b):这里,== 左边是一个包装类型,右边是两个包装类型之和,我们之前说==如果左右两边是包装类型,那么比较的是引用,但是这里很特殊啊,这里右边是一个操作数表达式,因此这里比较的就是值了

  2. g == (a+b):同样的,这里==右边的是一个操作数表达式:因此呢,比较的是值,所以返回true

  3. c.equals(a+b):这里用到了equals,equals又比较特殊了,我们说过,Integer和String是重写了equals的,内部是使用了intValue转换成int基本数据类型的,因此比较的是值,而这里由于存在操作数表达式,因此会触发一个自动拆箱和装箱的过程,a和b各自先拆成int类型进行求和,然后编译器会再调用valueof,对求和后的结果进行装箱,然后再回到equals内部比较,由于我们的c是一个Integer类型,我们根据上面对Integer源码的分析可以看到,它内部的equals会判断这个传入进来的类型是否属于Integer,如果也是Integer类型的话,那么就比较值了,所以最后的结果就是true

  4. g.equals(a+b):这个和上面差不多,也会触发自动拆箱和装箱的过程,但是呢,这里的g是一个Long包装类型,它的内部也重写了equals,不过它判断的是,传入进来的参数是否是Long类型,如果不是Long包装类型,那么直接就返回false了

  5. int1 == int2:这两个都是基本数据类型,那么比较的就是值了,没什么好说的

  6. int1 == integer1:这里==左边的是基本数据类型,右边的是包装类型,这个时候,会触发编译器的自动拆箱,调用Integer的intValue方法,因此比较的是值,返回的是true

  7. integer1 == integer2:我们在上面分析==和equals的区别的时候讲过,当==左右两边都是包装类型的时候,比较的就是引用,由于这里的integer1和integer2都是new出来的对象,两个的地址肯定是不同的,所以返回的就是false

  8. integer3 == a:这里integer3是一个new出来的对象,而a是一个存在于Integer包装类型cache中的常量,他们两个在内存中的位置是不一样的,因此返回false

以上就是关于自动装箱和自动拆箱的原理的解析了~

下一节,我们就来讲讲什么是Java的方法区以及Java的内存模型

おすすめ

転載: juejin.im/post/7079763194532757518