Java面试之Java基础7——(自动)装箱与(自动)拆箱

目录

装箱

拆箱

自动装箱

自动拆箱

装箱、拆箱的深入了解


装箱

概念:即将基本类型转换为对应的包装类型。它们的对应关系如下:

装箱的方法:

/*
    装箱:将基本类型的数据转换成包装类型,下面以Integer类为例:
        构造方法:
            Integer(int value)  构造一个新的Integer对象,将基本类型int类型的值包装成Integer对象
            Integer(String s)   构造一个新的Integer对象,将引用类型String类型的值包装转换成Integer对象
        静态方法:
            static Integer valueOf(int i)   返回一个表示指定int值的Integer实例对象
            static Integer valueOf(String s)    返回指定的String值的Integer实例对象
 */
public class Demo {
    public static void main(String[] args) {
        /*
            所以实现装箱有两种方法:
                构造函数
                    Integer i1 = new Integer(10);

                静态方法
                    Integer i2 = Integer.valueOf(10);
         */
        Integer i1=new Integer(10);
        System.out.println(i1);

        Integer i2=Integer.valueOf(10);
        System.out.println(i2);
    }
}

拆箱

概念:从包装类对象转换成对应的基本类型。

方法:

/*
    拆箱:从包装类对象转换成对应的基本类型
        成员方法:
            int intValue()  Integer对象调用该方法将转换成int基本数据类型
 */
public class Demo {
    public static void main(String[] args) {
        /*
            所以实现拆箱的一种方法:
                Integer i1 = new Integer(10);
                int i = i1.intValue();
         */
        Integer i1 = new Integer(10);
        int i = i1.intValue();
        System.out.println(i);
    }
}

自动装箱

概念:将基本类型的变量赋值给对应的包装类。

自动装箱发生的情况:

/*
    自动装箱:直接将基本数据类型转换对应的包装类型,下面以int->Integer为例
    自动装箱发生的情况:
        1.直接将基本数据类型赋值给包装类
            例如:Integer i = 10;
        2.集合方法添加元素
            例如:new ArrayList<Integer>().add(10);
 */
public class Demo {
    public static void main(String[] args) {
        // 1.直接将基本数据赋值给包装类
        // 相当于 Integer i = new Integer(10); 或 Integer i = Integer.valueOf(10);
        Integer i = 10;// 发生了自动装箱

        // 2.集合添加元素
        ArrayList<Integer> list = new ArrayList<>();
        list.add(10);// 发生了自动装箱
    }
}

自动拆箱

概念:将包装类型直接转换成对应的基本类型

自动拆箱发生的情况:

/*
    自动拆箱:直接将包装类型转换成对应的基本类型(包装类型无法直接参与运算,属于对象)。下面以Integer->int为例
    自动拆箱发生的情况:
        1.包装类型发生运算的时候
            例如:Integer i += 2;
        2.集合中获取元素时
            例如:new ArrayList<Integer>().get(2);
 */
public class Demo {
    public static void main(String[] args) {
        // 1.包装类型参与运算的时候发生自动拆箱
        // 相当于 i = i.intValue()
        Integer i = 2;// 发生了自动装箱
        i = i + 2;// 发生了自动拆箱

        // 2.获取集合中的元素
        List<Integer> list = new ArrayList<>();
        list.add(1);// 发生了自动装箱
        list.add(2);// 发生了自动装箱
        Integer integer = list.get(2);// 发生了自动拆箱
    }
}

装箱、拆箱的深入了解

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

参考博客:包装类的缓存及自动装箱、拆箱

在这篇博客中见到了比较有意思的是面试题,让我对装箱、拆箱有了更深刻的理解。

下面这个问题就是里面的一道面试题,询问输出的结果,在未看之前,觉得两个都是一样的,但输出结果确是大出所料:

public class Demo {
    public static void main(String[] args) {
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
        System.out.println(i1 == i2);
        System.out.println(i3 == i4);
    }
}
/**
 * 打印结果:
 * true
 * false
 */

下面认识一个javap命令,关于该命令的详解可以参考博客:https://www.cnblogs.com/frinder6/p/5440173.html

简单来说,javap命令就是对javac生成的class文件反编译,它的帮助如下:

我们要用到的就是javap -c命令参数。

使用CMD或者IDEA的Terminal跳转到Demo.class所在的目录下,然后执行javap -c Demo.class命令。

那么控制台就会打印下面的内容:

Compiled from "Demo.java"
public class Demo {
  public Demo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: bipush        100
       2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       5: astore_1
       6: bipush        100
       8: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      11: astore_2
      12: sipush        200
      15: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      18: astore_3
      19: sipush        300
      22: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      25: astore        4
      27: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      30: aload_1
      31: aload_2
      32: if_acmpne     39
      35: iconst_1
      36: goto          40
      39: iconst_0
      40: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
      43: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      46: aload_3

其实我也看不懂写了些什么,不过可以看双斜杠后面的注释,这是属于Java范畴的。

比如:

// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

意思是调用了java.lang包下的Integer类的valueOf()方法。

我们学过自动装箱、自动拆箱就知道Integer i1=100;就是对100进行了自动装箱,通过这里的注释我们明白了它的自动装箱是调用了Integer类的valueOf()方法来完成的。

Integer.valueOf(int i)方法正是将一个int类型的数装箱成Integer类型的。

除此之外,我们还需要明白"=="这个运算符的含义:如果“==”的左右两侧是基本数据类型,则比较的是值,如果是引用数据类型,那么比较的就是对象的地址值。

那么执行下下面的代码如何:

public class Demo {
    public static void main(String[] args) {
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
        System.out.println(i1 == i2);
        System.out.println(i3 == i4);
        System.out.println("i1地址值:"+System.identityHashCode(i1));
        System.out.println("i2地址值:"+System.identityHashCode(i2));
        System.out.println("i3地址值:"+System.identityHashCode(i3));
        System.out.println("i4地址值:"+System.identityHashCode(i4));
    }
}

注:System.identityHashCode(obj)可以来打印一个对象在内存中的地址值。

由于不同机器打印的值可能不一样,所以这里截图展示:

可以看到i1和i2的地址值相等,而==比较的正是两个对象的地址值,所以为true,但i3和i4的地址值居然不想等,所以就为false了,那么为什么是这样呢?

从上面javap反编译出来的代码中可以得知调用了Integer.valueOf()方法,那么去Integer.valueOf()方法的源码一窥究竟吧。

该方法的源码是这样的,注意参数类型是int的,不是其他重载方法:

注意它有一个比较范围IntegerCache.low和IntegerCache.high,通过阅读源码知道low为-128,high为127。

而IntegerCache的源码为:

    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 =
                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() {}
    }

从参考的博客来看,IntegerCache类是Java为包装类型采用的缓存机制,即在类加载时,初始化一定数量的常用数据对象(即常说的热点数据),放入缓存中,等到使用时直接命中缓存,减少资源浪费。

从下图来看,很多包装类都采用了缓存机制,除了Float和Double

其中i1和i2的值为100,正好在缓存类的范围内,就直接命中事先缓存好的Integer类对象,所以它们的地址值是一样的,都指向了同一个对象。

但i3和i4的值为200,超出了缓存类的范围,即超出了-128<=i<=127的范围,所以调用了new Integer(i),重新生成了一个Integer对象,故内存地址值不一样,所以返回false。

Byte、Short、Integer、Long中缓存类设置的范围都是-128到127。

Character类的缓存类中缓存数组的范围是128。

Boolean类只有一个TRUE或FALSE,没有缓存类:

TRUE和FALSE的值就是两个对象:

Float类和Double类没有缓存数组,所以都是创建新对象,所以内存地址不一样。

总结:

包装类缓存类中的缓存数组(cache)的范围
Byte-128~127
Short-128~127
Integer-128~127
Long-128~127
Float
Double
Character0~127
Boolean无,但对象不可变

所以,对于下面的结果应该很容易得出答案了。

public class Demo {
    public static void main(String[] args) {
        Byte b1 = 100;
        Byte b2 = 100;
        System.out.println(b1 == b2);

        Short s1 = 100;
        Short s2 = 100;
        Short s3 = 200;
        Short s4 = 200;
        System.out.println(s1 == s2);
        System.out.println(s3 == s4);

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

        Long l1 = 100L;
        Long l2 = 100L;
        Long l3 = 200L;
        Long l4 = 200L;
        System.out.println(l1 == l2);
        System.out.println(l3 == l4);

        Float f1 = 100F;
        Float f2 = 100F;
        Float f3 = 200F;
        Float f4 = 200F;
        System.out.println(f1 == f2);
        System.out.println(f3 == f4);

        Double d1 = 100D;
        Double d2 = 100D;
        Double d3 = 100D;
        Double d4 = 100D;
        System.out.println(d1 == d2);
        System.out.println(d3 == d4);

        Character c1 = 100;
        Character c2 = 100;
        Character c3 = 200;
        Character c4 = 200;
        System.out.println(c1 == c2);
        System.out.println(c3 == c4);

        Boolean boo1 = true;
        Boolean boo2 = true;
        Boolean boo3 = false;
        Boolean boo4 = false;
        System.out.println(boo1 == boo2);
        System.out.println(boo3 == boo4);
    }
}
/**
 * 打印结果:
 * true
 * true
 * false
 * true
 * false
 * true
 * false
 * false
 * false
 * false
 * false
 * true
 * false
 * true
 * true
 */

下面说下自动装箱和自动拆箱,看下面的代码,研究下运行结果:

public class Demo {
    public static void main(String[] args) {
        int i1 = 100;
        Integer i2 = 100;// i2发生了自动装箱
        System.out.println(i1 == i2);// i2由于参与了运算,所以发生了自动拆箱
        System.out.println(i2.equals(i1));// i1发生了自动装箱

        int i3 = 200;
        Integer i4 = 200;// i4发生了自动装箱
        System.out.println(i3 == i4);// i4由于参与了运算,所以发生了自动拆箱
        System.out.println(i4.equals(i3));// i3发生了自动装箱

        Long l5 = 200L;
        System.out.println(i4 == (i1 + i2));// i2由于参与了运算,所以发生了自动拆箱,所以(i1+i2)是一个int类型,而i4又参与了运算,所以发生自动拆箱,所以为true
        System.out.println(i4.equals(i1 + i2));
        System.out.println(l5 == (i1 + i2));
        System.out.println(l5.equals(i1 + i2));
    }
}
/**
 * 打印结果:
 * true
 * true
 * true
 * true
 * true
 * true
 * true
 * false
 */

解释如下:

public class Demo {
    public static void main(String[] args) {
        int i1 = 100;
        Integer i2 = 100;// i2发生了自动装箱
        /*
            i2由于参与了运算,所以发生了自动拆箱,所以比较的是i1和i2的值,都是100,所以返回true
         */
        System.out.println(i1 == i2);
        /*
            i1原本是int类型的,发生了自动装箱,变成了Integer类型,并且i1和i2的值还相等,所以返回true
         */
        System.out.println(i2.equals(i1));

        int i3 = 200;
        Integer i4 = 200;// i4发生了自动装箱
         /*
            i3由于参与了运算,所以发生了自动拆箱,所以比较的是i3和i4的值,都是200,所以返回true
         */
        System.out.println(i3 == i4);
        /*
            i3原本是int类型的,发生了自动装箱,变成了Integer类型,并且i3和i4的值还相等,所以返回true
         */
        System.out.println(i4.equals(i3));

        Long l5 = 200L;
        /*
            i2原本是Integer类型的,参与了加法(+)运算,发生自动拆箱,完成了与i1相加,得到的值是200,是int类型的
            i4是Integer类型的,参与了运算,并且等号的右边是int,所以发生自动拆箱,比较值,都是200相等,返回true
         */
        System.out.println(i4 == (i1 + i2));
        /*
            i2原本是Integer类型的,参与了加法(+)运算,发生自动拆箱,完成了与i1相加,得到的值是200,是int类型的,
            但在equal()方法中又发生自动装箱,这时候都是Integer类型并且值相等,所以返回true
         */
        System.out.println(i4.equals(i1 + i2));
        /*
            i2原本是Integer类型的,参与了加法(+)运算,发生自动拆箱,完成了与i1相加,得到的值是200,是int类型的
            l5是Long类型的,参与了运算,并且等号的右边是int类型,所以发生了自动拆箱,比较值,都是200相等,返回true
         */
        System.out.println(l5 == (i1 + i2));
        /*
            i2原本是Integer类型的,参与了加法(+)运算,发生自动拆箱,完成了与i1相加,得到的值是200,是int类型的,
            但在equal()方法中又发生自动装箱,注意,这时候l5是Long类型,而(i1+i2)是Integer类型,所以类型不相等,直接返回false
         */
        System.out.println(l5.equals(i1 + i2));
    }
}

对于最后一个为false,可以查看下equals()方法的源码:

要同时类型相同并且值相等equals方法才会返回true。

执行javap -c命令,查看反编译代码:

Compiled from "Demo.java"
public class Demo {
  public Demo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: bipush        100
       2: istore_1
       3: bipush        100
       5: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       8: astore_2
       9: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      12: iload_1
      13: aload_2
      14: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      17: if_icmpne     24
      20: iconst_1
      21: goto          25
      24: iconst_0
      25: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
      28: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: aload_2
      32: iload_1
      33: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      36: invokevirtual #6                  // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
      39: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
      42: sipush        200
      45: istore_3
      46: sipush        200
      49: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      52: astore        4
      54: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      57: iload_3
      58: aload         4
      60: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      63: if_icmpne     70
      66: iconst_1
      67: goto          71
      70: iconst_0
      71: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
      74: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      77: aload         4
      79: iload_3
      80: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      83: invokevirtual #6                  // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
      86: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
      89: ldc2_w        #7                  // long 200l
      92: invokestatic  #9                  // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
      95: astore        5
      97: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
     100: aload         4
     102: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
     105: iload_1
     106: aload_2
     107: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
     110: iadd
     111: if_icmpne     118
     114: iconst_1
     115: goto          119
     118: iconst_0
     119: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
     122: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
     125: aload         4
     127: iload_1
     128: aload_2
     129: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
     132: iadd
     133: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     136: invokevirtual #6                  // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
     139: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
     142: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
     145: aload         5
     147: invokevirtual #10                 // Method java/lang/Long.longValue:()J
     150: iload_1
     151: aload_2
     152: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
     155: iadd
     156: i2l
     157: lcmp
     158: ifne          165
     161: iconst_1
     162: goto          166
     165: iconst_0
     166: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
     169: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
     172: aload         5
     174: iload_1
     175: aload_2
     176: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
     179: iadd
     180: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     183: invokevirtual #11                 // Method java/lang/Long.equals:(Ljava/lang/Object;)Z
     186: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
     189: return
}

既有装箱又有拆箱。

总结

  • 当一个基础数据类型与封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算。 
  • 关于obj1.equals(obj2)方法,其中obj1和obj2的数据类型必须相同,并且值相等才会返回true,否则返回false。
  • 当 “==”运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。
  • 注意不要写出这样的代码,虽然编译通过,但不合法。Integer i=null; int n=i;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值