前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >与IntegerCache有关的一个比较坑的面试题

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

作者头像
冬天里的懒猫
发布2020-08-11 14:50:28
3560
发布2020-08-11 14:50:28
举报

最近在学习有关常量池的内容,正好看到一篇与之有关的面试题的文章。题目如下:

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的过程中可以查看任何变量的值,你只需要在代码中选中即可。

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述

这就说明我们实际上修改第一个位置是完成了。第一个值变成了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一个新的对象。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-08-04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档