探究java Integer的实现

我们先来看一个例子

public static void main(String[] args) {
    Integer a = 1,b=2;
    System.out.println("交换前a="+a+ ",b="+b);
    // 交换a,b值
    exchange(a,b);
    System.out.println("交换后a="+a+ ",b="+b);
}
public static void exchange(Integer o1,Integer o2){
  Integer temp = o1;
  o1=o2;
  o2=temp;
}

运行结果:

        交换前a=1,b=2
		交换后a=1,b=2

按照预期结果a和b值应该被交换,但实际结果却是a和b的值根本就没有变化,这是怎么回事呢?

我们知道java中有两种传递方式

引用传递:对象类型值存储在堆中,而所对应的引用即堆中的地址存放在栈中。即传递的是这个对象地址。

值传递:基本数据类型用到都是值传递,会直接copy一个原对象的副本。

我们还知道对象类型和引用类型存放的位置是不同的 对象类型存储在堆中,引用类型存储在栈中
对象类型使用的是引用传递。

而Integer是特殊的引用类型即封装类型,java中规定传递他们的副本。

我们通过下面两幅图来了解下这个过程

交换前

交换后

所以实际进行交换的仅仅是a,b的副本,并非a,b本身。

不过你仍然可能有疑问,为什么Integer要规定传递副本呢?

我们可以通过阅读Integer的源码找到答案:

Integer部分源码

可以看到Integer的value被final修饰,意味着值不可变。

那么你可能会问,该怎么实现正确的交换呢?

我们知道value作为私有的成员变量是不可访问的,但是我们可以通过反射来跳过权限检查,从而达到操作的目的。

我们对exchange()方法做一些修改如下:

public static void exchange(Integer o1,Integer o2)  {
    try {
        // 通过反射获取Integer的value属性
        Field field =Integer.class.getDeclaredField("value");
        // 设置该值为true,以绕开权限检查
        field.setAccessible(true);
        int temp = o1.intValue();
        field.set(o1,o2.intValue());
        field.set(o2,temp); 
    }catch (Exception e){
        e.printStackTrace();
    }
}

再次运行,运行结果如下:

交换前a=1,b=2
交换后a=2,b=2

###发现了吗?只有a变了,b却没变,这是为啥啊?

其实这个问题就涉及到了java中的一个语法糖-自动装箱与拆箱

  • 自动装箱:自动将基本数据类型转换为包装器类型。如下:

      Integer a = 1,b=2;
    

    相当于:

      Integer a = Integer.valueOf(1),b=Integer.valueOf(2);
    

    先看看valueOf()的源码:

      public static Integer valueOf(int i) {
      if (i >= IntegerCache.low && i <= IntegerCache.high)
          return IntegerCache.cache[i + (-IntegerCache.low)];
      return new Integer(i);
      }
    

    IntegerCache 的代码:

      private static class IntegerCache {
      static final int low = -128;
      static final int high;
      static final Integer cache[];
    

可以看到在转换的时候,如果目标值在low和high之间 ,也就是在-128到127之间,会直接从Cache缓存中取值。 如果目标值超出这个范围则会new 一个Integer对象。

这里需要格外注意下:

    Integer a =1,b=1;
	return a==b; 
    该比较结果为true,因为a,b的值在-128~127,从缓存取值相同,为同一地址。

    ----------------------------------------------------------------- 
	Integer a =129,b=129;
	return a==b; 
    该比较结果为false,因为a,b的值超出-128~127,会分别new一个Integer,分配的是不同的内存地址。
  • 自动拆箱: 自动将包装器类型转换为基本数据类型。如下:

       Integer total = 99;
       //自定拆箱
       int totalprim = total;
    

了解了自动装箱的原理,我们再来分析一下这个问题。我们现在知道了 field.set(o2,temp); 中的temp使用了自动装箱的操作 ,相当于 filed.set(02,Integer.valueOf(temp).intValue) 在执行valueOf()方法时,会根据数组下标从缓存取值 而temp的值等于i1,i1的下标地址取值为2,所以temp也为2 。于是最后 b =2。

解决办法

1、将temp定义为Integer避免自动装箱缓存问题

int temp = o1.intValue(); --> Integer temp = o1.intValue();

2、set时避免自动装箱

field.set(o1,o2.intValue()); --> field.setInt(o1,o2.intValue());

field.set(o2,temp); --> field.setInt(o2,temp);

修改后运行结果:

交换前a=1,b=2
交换后a=2,b=1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值