持续整理,持续学习

参考:深入剖析Java中的装箱和拆箱、

一、自动拆箱/装箱概述 

装箱就是自动将基本数据类型转换为包装器类型;拆箱就是 自动将包装器类型转换为基本数据类型。

注意:自动装箱和自动拆箱只发生在编译阶段,目的是为了少写代码。

在JavaSE5之前,如果生成一个值为10的Integer对象。必须通过:

Integer i = new Integer(10);

但是在Java SE5开始就提供了自动装箱的特性 

int(4字节)

Integer

byte(1字节)

Byte

short(2字节)

Short

long(8字节)

Long

float(4字节)

Float

double(8字节)

Double

char(2字节)

Character

boolean(未定)

Boolean

二、拆箱/装箱代码理解

package com.example.anonotationnormal.boxing;

/**
 * @author :luoyu
 * @version :1.0
 * @date : 2021/10/13 11:35 上午
 * @description
 */

public class MainTest {
    public static void main(String[] args) {
        //自动装箱,底层其实执行了Integer i=Integer.valueOf(10);
        Integer i = 10;
        //自动拆箱,底层其实执行了int n=i.intValue();
        int n = i;
    }
}

生成的Class文件

java自动拆装箱怎么实现的 自动装箱拆箱 java_System

 找到对应的class文件,进行反编译:javap -c xxx

java自动拆装箱怎么实现的 自动装箱拆箱 java_面试_02

Integer的valueOf(int i)方法可以将一个基本数据类型转化为对应的包装类型,即装箱方法。  

Integer的intValue()方法则可以将一个包装类型转化为对应的基本数据类型,即拆箱方法。

注意:自动装箱和自动拆箱只发生在编译阶段,目的是为了少写代码。

装箱和拆箱会影响代码的执行效率,因为编译后的class代码是严格区分基本类型和引用类型的。并且,自动拆箱执行时可能会报NullPointerException

public class MainTest {
    public static void main(String[] args) {
        Integer n = null;
        int i = n;
    }
}

运行结果:

Exception in thread "main" java.lang.NullPointerException
    at com.example.anonotationnormal.boxing.MainTest.main(MainTest.java:13) 

所以:装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。 

三、Integer是什么?

public final class Integer extends Number implements Comparable<Integer> {
    ...
}

Integer是int的包装类,如下图,可以将valueOf将int装箱为Integer,可以通过intValue将Integer拆箱为int

java自动拆装箱怎么实现的 自动装箱拆箱 java_Boo_03

同时Integer包装类也是不变类(final修饰)。一旦创建了Integer对象,该对象就是不变的。这里的不变可以理解为“地址”,后续赋值,只是指向的位置变化 

四、区别?

装箱:利用Integer的构造方法Integer(int value),即Integer c=new Integer(1);

自动装箱:或者叫隐式装箱,直接给Integer赋值,即Integer d=1,在编译的时候,会调用Integer.valueOf()方法完成装箱。

相比而言,自动装箱可能比装箱具有更高的效率,体现在自动装箱的缓存上,下面从几道题目来讲自动装箱的缓存。

五、相关面试题目

 案例1:

package com.luoyu;
 
public class TestBox2 {
    public static void main(String[] args) {
        Integer a = 100;
        Integer b = 100;
        Integer c = 200;
        Integer d = 200;
        System.out.println(a == b);
        System.out.println(c == d);
    }
}

运行结果:

true

false

刚才我们知道,Integer a=100这条语句会触发自动装箱,而自动装箱的方法为Integer.valueOf()方法,让我们去寻找这个方法,一探究竟。

观察Integer类的源码中的valueOf()

/**
     * 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);
    }
private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

可以看得出,当i的值位于[-128,127]的时候,会直接返回Integer缓存数组中相应对象的引用,如果i大于127或小于-128,会重新创建一个Integer实例,并返回。

那么第一条式子a和b的值都在缓存范围内,因此他们指向同一个对象,因此返回true。c和d的值不在范围内,都是通过new创建出来的,因此不是同一个对象,返回false。

注意:Byte、Short、Integer、Long、Character、Double实现机制类似

缓存的意义是什么?

如上可以知道:Integer.valueOf()对于较小的数,始终返回相同的实例。本质上是因为Java标准库的Integer内部有缓存优化

  • 方法1:Integer n = new Integer(100);
  • 方法2:Integer n = Integer.valueOf(100);

方法2更好,因为方法1总是创建新的Integer实例,方法2把内部优化留给Integer的实现者

我们把能创建“新”对象的静态方法称为静态工厂方法Integer.valueOf()就是静态工厂方法,它尽可能地返回缓存的实例以节省内存。

创建新对象时,优先选用静态工厂方法而不是new操作符。

🤔️:如果我们考察Byte.valueOf()方法的源码,可以看到,标准库返回的Byte实例全部是缓存实例,但调用者并不关心静态工厂方法以何种方式创建新实例还是直接返回缓存的实例。

案例二:Double

public class MainTest {
    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
false 

 Double的ValueOf实现

public static Double valueOf(double d) {
        return new Double(d);
    }

也就是说:Double类的valueOf方法会采用与Integer类的valueOf方法不同的实现。为什么?因为在某个范围内的整型数值的个数是有限的,而浮点数却不是。 

注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。

Double、Float的valueOf方法的实现是类似的。

案例三:Boolean

public class MainTest {
    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 

Boolean的ValueOf方法实现【原因可知。】

public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

如上的TRUE和FALSE代表的意思是?静态的实例

public final class Boolean implements java.io.Serializable,
                                      Comparable<Boolean>
{
    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code true}.
     */
    public static final Boolean TRUE = new Boolean(true);

    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code false}.
     */
    public static final Boolean FALSE = new Boolean(false);

案例四:

public class MainTest {
    public static void main(String[] args) {

        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;

        Integer x = 1;
        Integer y = 320;
        Integer z = 150;
        Integer o = 170;
        int l = 3;

        System.out.println(b==3);
        System.out.println(c==d);
        System.out.println(e==f);
        System.out.println(c==(a+b));
        System.out.println(e==(g+h));
        System.out.println(y==(z+o));
        System.out.println(c.equals(a+b));
        System.out.println(g==(a+b));
        System.out.println(g.equals(a+b));
        System.out.println(g.equals(a+h));
    }
}

运行结果:

false
true
false
true
false
true
true
true
false
tru

来:查看下class文件

java自动拆装箱怎么实现的 自动装箱拆箱 java_java自动拆装箱怎么实现的_04

所以:

1. 当包装类型跟基本类型进行比较,或者计算的时候,都会进行拆箱为基本类型(Integer-》int)

2. 如果 两个都是包装类型,如:Integer,那么会直接比较。但是判断相等不建议使用“==”,建议使用equals

3. 但凡涉及到算术运算的时候,都会进行自动拆箱

4. 如果是equals,那就都是引用类型。会自动装箱

六、静态parseInt方法理解

将字符串解析为int

int x1 = Integer.parseInt("100"); // 100
int x2 = Integer.parseInt("100", 16); // 256,因为按16进制解析

将整数转化为字符串

public class Main {
    public static void main(String[] args) {
        System.out.println(Integer.toString(100)); // "100",表示为10进制
        System.out.println(Integer.toString(100, 36)); // "2s",表示为36进制
        System.out.println(Integer.toHexString(100)); // "64",表示为16进制
        System.out.println(Integer.toOctalString(100)); // "144",表示为8进制
        System.out.println(Integer.toBinaryString(100)); // "1100100",表示为2进制
    }
}

总结:

Java核心库提供的包装类型可以把基本类型包装为class

自动装箱和自动拆箱都是在编译期完成的(JDK>=1.5);

装箱和拆箱会影响执行效率,且拆箱时可能发生NullPointerException

包装类型的比较必须使用equals()

整数和浮点数的包装类型都继承自Number

包装类型提供了大量实用方法。