美文网首页Java
与IntegerCache有关的一个比较坑的面试题

与IntegerCache有关的一个比较坑的面试题

作者: 冬天里的懒喵 | 来源:发表于2020-08-11 20:16 被阅读0次

最近在学习有关常量池的内容,正好看到一篇与之有关的面试题的文章。Java大型互联网公司经典面试题,论JDK源码的重要性的无限思考。题目如下:

public static void main(String[] args) {

    Integer a = 1;
    Integer b = 2;
    System.out.println("before swap: a="+a+" b="+b);
    swap(a,b);
    System.out.println("before swap: a="+a+" b="+b);
}


private static void swap(Integer a,Integer b) {

}

要求将这两个Integer变量经过swap方法之后完成互换。

对于这个题目,可能一开始想到的就是最简单的办法,直接在swap方法内实现互换即可。不妨尝试一下:

private static void swap(Integer a,Integer b) {
    a = a^b;
    b = a^b;
    a = a^b;
}

但是输出结果如下:

before swap: a=1 b=2
before swap: a=1 b=2

没有成功。也就是说这个题目没有看上去的那么简单。里面肯定有坑。
这里就需要了解下java中引用传递还是值传递了。对于java,如果方法中传递的是基本类型,那么就是值传递。如果方法中是普通对象,那么就是引用传递。但是在java中,有这么一些特殊的类,如String、Integer等,没有set方法的immutable类。实际上也是值传递。
为此我们可以看看Integer的源码:

    /**
     * The value of the {@code Integer}.
     *
     * @serial
     */
    private final int value;

其中Integer的value是final修饰的。那么final修饰的只能被实例化一次。之后不能修改。因此Integer也没有提供可以修改Integer的set方法。因此Integer是一个与String类型的immutable类。所以也是值传递。
因此基于对反射的了解,我们解决这个问题的方法很容易想到了通过反射来解决。
那么我们修改代码如下:

private static void swap(Integer a,Integer b) {
    try {
        Field field = Integer.class.getDeclaredField("value");
        field.setAccessible(true);
        int tmp1 = a.intValue();
        int tmp2 = b.intValue();
        tmp1 = tmp1^tmp2;
        tmp2 = tmp1^tmp2;
        tmp1 = tmp1^tmp2;
        field.set(a,tmp1);
        field.set(b,tmp2);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

再次执行。
结果如下:

before swap: a=1 b=2
before swap: a=2 b=2

这个结果有些让人意外的是,a的结果改成了2,但是b的结果也是2 !!!!

对于这个问题,只能进行单步了。

在debug的时候发现,调用了valeOf方法。也就是说,jvm自动将int的数据通过valueOf方法转换为了Integer。
我们将这个类的class反编译,可以看到:

  public static void main(String[] args) {
    Integer a = Integer.valueOf(1);
    Integer b = Integer.valueOf(2);
    System.out.println("before swap: a=" + a + " b=" + b);
    swap(a, b);
    System.out.println("before swap: a=" + a + " b=" + b);
  }


  
  private static void swap(Integer a, Integer b) {
    try {
      Field field = Integer.class.getDeclaredField("value");
      field.setAccessible(true);
      int tmp1 = a.intValue();
      int tmp2 = b.intValue();
      tmp1 ^= tmp2;
      tmp2 = tmp1 ^ tmp2;
      tmp1 ^= tmp2;
      field.set(a, Integer.valueOf(tmp1));
      field.set(b, Integer.valueOf(tmp2));
    } catch (Exception e) {
      e.printStackTrace();
    } 
  }

实际上在jvm中执行的是上述代码。jvm会自动在这些类型需要转换的地方装箱。哪我们再看看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);
    }

这个方法,会对传入的值进行判断,如果在-128 - 127之间。则直接使用的是IntegerCache的结果。
那么这个题目要将Integer对象值为1的变量a和值为2的变量b互换,如果要成功的话,那就意味着将IntegerCache中的内容互换。
我们继续debug,在第一个set的代码行执行完成之后:

field.set(a, Integer.valueOf(tmp1));

此时我们可以看看这个IntegerCache是多少,为此为了debug方便我们加上一行:

Integer c = 5;

在这行执行valueOf方法的时候,查看IntegerCache中的内容:
idea比较好的就是在debug的过程中可以查看任何变量的值,你只需要在代码中选中即可。


image.png

如果出现显示不全,那么移动到最下面,双击。


image.png

可以看到数组中129和130两个位置都是2。


image.png

这就说明我们实际上修改第一个位置是完成了。第一个值变成了2,但是在第二次set的时候出现了问题,反编译之后的写法,实际上是Integer.ValueOf,那么去从IntegerCache中查找1对应的位置,但是此时已经修改为了2,那么得到2后再修改,任然还是2。
因此,为了解决这个问题,我们的代码做出修改,让set的时候不再走IntegerCache。修改为如下:

    private static void swap(Integer a,Integer b) {
        try {
            Field field = Integer.class.getDeclaredField("value");
            field.setAccessible(true);
            int tmp1 = a.intValue();
            int tmp2 = b.intValue();
            tmp1 = tmp1^tmp2;
            tmp2 = tmp1^tmp2;
            tmp1 = tmp1^tmp2;
            field.set(a,new Integer(tmp1));
            field.set(b,new Integer(tmp2));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这样就解决了此问题。我们可以再看一下反编译之后的代码:

  
  private static void swap(Integer a, Integer b) {
    try {
      Field field = Integer.class.getDeclaredField("value");
      field.setAccessible(true);
      int tmp1 = a.intValue();
      int tmp2 = b.intValue();
      tmp1 ^= tmp2;
      tmp2 = tmp1 ^ tmp2;
      tmp1 ^= tmp2;
      field.set(a, new Integer(tmp1));
      field.set(b, new Integer(tmp2));
    } catch (Exception e) {
      e.printStackTrace();
    } 
  }

可以看到通过new一个新对象的方法,就避免了再次进入IntegerCache。

总结:
以上这个面试题的坑不少,主要有:

  • 1.java中的方法传递和值传递。对于不可变类型实际上还是值传递。
  • 2.反射修改私有变量需要setAccessible(true)。
  • 3.自动装箱调用ValueOf方法会走IntegerCache。
  • 4.如果不想走IntegerCache,那么new一个新的对象。

相关文章

  • 与IntegerCache有关的一个比较坑的面试题

    最近在学习有关常量池的内容,正好看到一篇与之有关的面试题的文章。Java大型互联网公司经典面试题,论JDK源码的重...

  • Integer内部常量池实现详解

    在Integer类的内部有一个IntegerCache的内部类,这个就是Integer常量池实现的有关类。 ...

  • IntegerCache面试题

    前两天去面试,面试题如下: 输出的结果为: 看到这里有的小伙伴可能比较疑惑了,我们知道==比较的是对象的引用,那这...

  • iOS面试中踩到的坑

    面试的坑,不同的基础就会有不同的坑,不多说了直接上面试题:(面试题与答案会持续更新) 1、简述关键字assign、...

  • Integer的IntegerCache

    首先我们对两个Integer进行比较 上面的程序允许结果如下: Java 编译器把原始类型自动转换为封装类的过程称...

  • 有趣的IntegerCache

    一个很有趣的现象,下面这两个结果输出的结果是false true,这是为什么? 翻看Integer的源码可以...

  • IntegerCache

    参考链接:IntegerCache的妙用和陷阱! 我们知道==比较的是对象的引用,那这里为什么会这出这种情况呢? ...

  • IntegerCache

    int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。 Intege...

  • Integer 源码

    1. 静态内部类 IntegerCache

  • 猿学-Java面试考试问答题学会机智回答提高你的逼格

    Java面试题 List和Set比较,各自的子类比较 对比一:Arraylist与LinkedList的比较 1、...

网友评论

    本文标题:与IntegerCache有关的一个比较坑的面试题

    本文链接:https://www.haomeiwen.com/subject/exzydktx.html