一道关于Integer的面试题,你真的会了吗?

今天分享一道关于Integer的面试题。

1 面试题

面试题是这样的, 要手写打印结果:

public static void main(String[] args) {
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 128;
        Integer f = 128;
        Long g = 3L;
        System.out.println(c == d);//true
        System.out.println(e == f);//false
        System.out.println(c.equals(d));//true
        System.out.println(e.equals(f));//true
        System.out.println(c == (a + b));//true
        System.out.println(c.equals(a + b));//true
        System.out.println(g == (a + b));//true
        System.out.println(g.equals(a + b));//false
}


大家可以先自己手写一下结果,然后对比一下机器的运行结果,看看自己做对了几题。

我在深入研究这道题之前,以为很简单,对于这一块也是一知半解,深入研究之后才知道原来事情没那么简单。


2 装箱拆箱概念

在分析代码之前,我们需要先了解一些关于拆箱装箱的概念,如下一段代码:

public static void main(String[] args) {
        Integer a = 1;//1 调用静态方法Integer.valueOf自动装箱
        int b = a;//2调用对象方法Integer.intValue自动拆箱
}

javac后的class, 经过javap -c反编译后字节码如下:

 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

我们看到在注释1处,调用了静态方法Integer.valueOf自动装箱:
Integer a = Integer.valufOf(1);

而在注释2处调用的是对象方法Integer.intValue自动拆箱:
int b = a.intValue();

自动装箱和拆箱其实就这么简单,但是加上== equals以及运算符的话,使用起来就没那么简单了,下面开始分析面试题吧。

3 分析

我们拆分来看上面的面试题, 先看这么一段

public static void main(String[] args) {
        Integer c = 3;
        Integer d = 3;
        Integer e = 128;
        Integer f = 128;
        System.out.println(c == d);//true
        System.out.println(e == f);//false
        System.out.println(c.equals(d));//true
        System.out.println(e.equals(f));//true
}

我们知道==对于基本类型来讲是比较的值是否相等,对于引用数据来讲,比较的是地址值知否相等。equals就是比较的值是否相等。

那么上面的第一行和第二行的打印,可能有些人就有点蒙了。c == d 或者 e == f都是对象自动装箱后比较的地址值,为什么一个是true, 一个是false呢?

想知道这个的根本原因就得看==两边的两个对象到底是什么,上面说了自动装箱调用的是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);
    }

从源码中结合注释能看出来,调用valueOf方法的时候的逻辑是,如果i的值在[-128,127]区间内的话,是从IntegerCache的cache数组中取值,否则的话就是new Integer返回一个新的值。

我们看一下IntegerCache类,它是一个Integer的私有的静态内部类:

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

从静态代码块中能够看出,IntegerCache类在加载的时候就缓存了从[-128,127]范围内的Integer对象到cache[]数组中,此数组一共有256个元素,
最小元素cache[0] = -128, 最大元素是cache[255] = 127;

分析到这里,上面的第一行和第二行的打印结果原因,自然就清晰了,因为3位于此cache数组缓存中,所以c和d是同一个对象,而e和f是128,超出了[-128,127]的取值范围,返回的是两个新new的对象,自然返回结果是false。


下面分析第三行第四行的打印结果, equals方法的源码如下:
public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
}

在使用equals比较的时候,首先判断比较的两个对象是否是同一种类型:
如果不是就直接返回false;如果是就拆箱比较它们的值是否相等。所以这里第三行和第四行因为类型一致,并且值的大小相等,所以结果都为true。


接着我们看面试题的第五第六行的分析:

public static void main(String[] args) {
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        System.out.println(c == (a + b));//true
        System.out.println(c.equals(a + b));//true
    }


反编译后的字节码如下:

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: iconst_2
       6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       9: astore_2
      10: iconst_3
      11: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      14: astore_3
      15: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      18: aload_3
      19: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      22: aload_1
      23: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      26: aload_2
      27: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      30: iadd
      31: if_icmpne     38
      34: iconst_1
      35: goto          39
      38: iconst_0
      39: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
      42: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      45: aload_3
      46: aload_1
      47: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      50: aload_2
      51: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      54: iadd
      55: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      58: invokevirtual #6                  // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
      61: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
      64: return

字节码中1 6 11行是对a b c 变量的装箱操作,我们这里不用关心。

对于c == (a + b)打印结果: 在19 23 27行的时候,使用aload指令从局部变量表中取出上面调用astore存储在局部变量表中的元素放入操作数栈中,并且调用Integer.intValue()方法将其拆箱进行比较,那比较结果自然就和值是否相等有关了。所以第五行结果为true。

对于c.equals(a + b)打印结果:从字节码的47 51行看出先是调用的Integer.intValue对a b进行拆箱后相加,然后调用Integer.valueOf进行装箱操作和c进行equals比较,上面分析过equals方法的源码,如果类型一致的话,比较的就是值是否相同,这里因为值相同,所以返回的就是true。


下面分析面试题的第七第八行:

public static void main(String[] args) {
        Integer a = 1;
        Integer b = 2;
        Long g = 3L;
        System.out.println(g == (a + b));//true
        System.out.println(g.equals(a + b));//false
}

反编译后的字节码如下:

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: iconst_2
       6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       9: astore_2
      10: ldc2_w        #3                  // long 3l
      13: invokestatic  #5                  // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
      16: astore_3
      17: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      20: aload_3
      21: invokevirtual #7                  // Method java/lang/Long.longValue:()J
      24: aload_1
      25: invokevirtual #8                  // Method java/lang/Integer.intValue:()I
      28: aload_2
      29: invokevirtual #8                  // Method java/lang/Integer.intValue:()I
      32: iadd
      33: i2l
      34: lcmp
      35: ifne          42
      38: iconst_1
      39: goto          43
      42: iconst_0
      43: invokevirtual #9                  // Method java/io/PrintStream.println:(Z)V
      46: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      49: aload_3
      50: aload_1
      51: invokevirtual #8                  // Method java/lang/Integer.intValue:()I
      54: aload_2
      55: invokevirtual #8                  // Method java/lang/Integer.intValue:()I
      58: iadd
      59: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      62: invokevirtual #10                 // Method java/lang/Long.equals:(Ljava/lang/Object;)Z
      65: invokevirtual #9                  // Method java/io/PrintStream.println:(Z)V
      68: return

对于g == (a + b): 和上面的分析一样,21 25 29行都是拆箱操作,虽然g是Long类型,但是这里调用了longValue拆箱,最终比较的是值,所以结果为true。

对于g.equals(a + b):在51 55行对a b拆箱后相加,然后再装箱和Long.equals方法,Long.equals方法源码如下:

public boolean equals(Object obj) {
        if (obj instanceof Long) {
            return value == ((Long)obj).longValue();
        }
        return false;
}

从源码中可以看到和Long和Integer的equals方法一样,都是先比较对象类型是否一致,不一致就返回false, 一致的话就比较值,这里很明显类型不一致,因为3是Long类型,a+b装箱操作后的类型还是Integer类型,所以返回false。


最后将前面的面试题全部贴出来,附上分析:
public static void main(String[] args) {
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 128;
        Integer f = 128;
        Long g = 3L;
        System.out.println(c == d);//true 3在[-128,127]区间 返回的是缓存,所以地址值一致,true
        System.out.println(e == f);//false 128不在[-128,127]区间,返回的新new的Integer,所以false
        System.out.println(c.equals(d));//true equals方法先看类型是否一致,然后比较的intValue,此处所以true
        System.out.println(e.equals(f));//true equals方法在值相等的情况下,和值大小无关。先看类型,再比较值大小是否相等,此处为true
        System.out.println(c == (a + b));//true ab拆箱相加,和拆箱后的c比较值,自然相等;就算大于127也是相等的,此处为true
        System.out.println(c.equals(a + b));//true equals a b先拆箱,相加后装箱,先比较类型,再比较值,此处为true
        System.out.println(g == (a + b));//true
        System.out.println(g.equals(a + b));//false 因为这里g的类型是Long,和a+b装箱的类型Integer不一致,所以为false
}

4 总结

  • 如果包装类型的==直接比较的话,比较的是地址值,比如Integer如果在[-128,127]之间的话,就是相等,否则就是new Integer,不相等
  • 如果包装类型的equals直接比较的话,比较是否是同一种instance of类型,如果不是同一种类型的话直接返回false;然后intValue比较值是否相等。
  • 如果包装类型和基本类型==比较的话,就拆箱比较(自行验证一下)
  • 如果包装类型和基本类型equals的话,就装箱比较(自行验证一下)

5 扩展题

public static void main(String[] args) {
        Long a = 1L;
        Long b = 2L;
        Long c = 3L;
        Long d = 3L;
        Long e = 128L;
        Long f = 128L;
        Double g = 3.0;
        System.out.println(c == d);
        System.out.println(e == f);
        System.out.println(c.equals(d));
        System.out.println(e.equals(f));
        System.out.println(c == (a + b));
        System.out.println(c.equals(a + b));
        System.out.println(g == (a + b));
        System.out.println(g.equals(a + b));
    }

看完上面分析,相信你已经对装箱拆箱有了深入的理解,那么再做一道题练练手吧?







答案和上面面试题答案一致

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值