Integer传参与值的修改

文章围绕Integer作为形参传入及修改值问题展开研究。通过swap方法交换a和b的值的案例,解析地址传参和自动装箱机制。探讨如何不改变地址改变Integer的值,发现反射是可行方式,还分析了反射修改值时不同方法结果不同的原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前看了一篇文章是关于Integer作为形参传入,之后修改值得问题,感觉有点意思,又做了进一步研究。

案例:

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

原本的案例是这样: 通过swap方法将a和b的值交换

测试一:

// 1
private static void swap(Integer a, Integer b) {
	int temp = a;
	a = b;
	b = temp;
}

最传统的方式,如果是在main中是可以实现的,但在swap中的结果是:

a=1,b=2
a=1,b=2

解析:

  1. 这里是地址传参,b将地址赋值给a,在方法外,a的值一定是不变的
  2. b看似是被一个int类型赋值之后做了自动装箱,但实际上,经过调试发现自动装箱调用的是Integer.valueOf方法
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

根据源码可以发现,无论是哪个分支都不会影响main方法中的b的值

  • 在-128~127这个神奇的区间内是在IntegerCache.cache 中获取Integer对象,而IntegerCache.cache是一个静态数组,作为这个区间值的缓存,所以以自动装箱创建的Integer对象是已经创建好的,统一数值其地址相同
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];
  • 在上述区间之外,重新创建Integer对象,地址改变,而不是值改变,因此不影响main中b的值。 同理,2的结果是一样的
// 2
private static void swap2(Integer a, Integer b) {
	int temp = a.intValue();
	a = b.intValue();
	b = temp;
}

所以如何改变Integer的值,而不改变地址?之前看到的文章中给出了答案,我也只想到了这种方式:反射

测试六:

// 6
private static void swap6(Integer a, Integer b) {
	Integer temp = a.intValue();
	try {
		Field field = Integer.class.getDeclaredField("value");
		field.setAccessible(true);
		field.set(a, b);
		System.out.println("swap: temp:" + temp);
		field.set(b, temp);
	} catch (NoSuchFieldException | SecurityException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} catch (IllegalArgumentException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} catch (IllegalAccessException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}

结果:

a=1,b=2
a=2,b=2

a已经成功改变,但是b却没有,原因:temp与a的地址相同,在第一次修改值之后a从1变为2,temp也变为2,所以b不会改变

那么temp改为int呢?

// 5
private static void swap5(Integer a, Integer b) {
	int temp = a.intValue();
	try {
		Field field = Integer.class.getDeclaredField("value");
		field.setAccessible(true);
		field.set(a, b);
		System.out.println("swap: temp:" + temp);
		Integer c = temp;
		System.out.println(c.intValue());
		field.set(b, temp);
	} catch (NoSuchFieldException | SecurityException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} catch (IllegalArgumentException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} catch (IllegalAccessException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}

结果仍然为

a=1,b=2
a=2,b=2

很令人费解,于是在中间添加了

Integer c = temp;
System.out.println(c.intValue());

发现temp输出结果是1,但c却是2?为什么?

a=1,所以temp为1没有问题,关键在于

  • Integer c = temp; 和上述一样,调用了Integer.valueOf(1);获取到的是IntegerCache.cache[129],即原来a的位置,但是a已经被set为2了,所以IntegerCache.cache中原来1的位置被替代为2,所以c的value为2.

但是还有另一个因素导致,先看下面的例子:

// 3
private static void swap3(Integer a, Integer b) {
	int temp = a.intValue();
	try {
		Field field = Integer.class.getDeclaredField("value");
		field.setAccessible(true);
		field.setInt(a, b);
		field.setInt(b, temp);
	} catch (NoSuchFieldException | SecurityException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} catch (IllegalArgumentException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} catch (IllegalAccessException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}

3的结果是:

a=1,b=2
a=2,b=1

是我众多例子中唯一修改了的。

3和5的区别在于field.setInt(b, temp);field.set(b, temp);

看一下分别的源码:

Field:
public void set(Object obj, Object value)
    throws IllegalArgumentException, IllegalAccessException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    getFieldAccessor(obj).set(obj, value);
}
UnsafeQualifiedIntegerFieldAccessorImpl:
public void set(Object arg0, Object arg1) throws IllegalArgumentException, IllegalAccessException {
		this.ensureObj(arg0);
    if (this.isReadOnly) {
    	this.throwFinalFieldIllegalAccessException(arg1);
    }
    
    if (arg1 == null) {
    	this.throwSetIllegalArgumentException(arg1);
    }
    
    if (arg1 instanceof Byte) {
    	unsafe.putIntVolatile(arg0, this.fieldOffset, ((Byte) arg1).byteValue());
    } else if (arg1 instanceof Short) {
    	unsafe.putIntVolatile(arg0, this.fieldOffset, ((Short) arg1).shortValue());
    } else if (arg1 instanceof Character) {
    	unsafe.putIntVolatile(arg0, this.fieldOffset, ((Character) arg1).charValue());
    } else if (arg1 instanceof Integer) {
    	unsafe.putIntVolatile(arg0, this.fieldOffset, ((Integer) arg1).intValue());
    } else {
    	this.throwSetIllegalArgumentException(arg1);
    }
}
Field:
public void setInt(Object obj, int i)
        throws IllegalArgumentException, IllegalAccessException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    getFieldAccessor(obj).setInt(obj, i);
}
UnsafeQualifiedIntegerFieldAccessorImpl:
public void setInt(Object arg0, int arg1) throws IllegalArgumentException, IllegalAccessException {
		this.ensureObj(arg0);
	if (this.isReadOnly) {
		this.throwFinalFieldIllegalAccessException(arg1);
	}

	unsafe.putIntVolatile(arg0, this.fieldOffset, arg1);
}

两中方式最终都是执行了UnsafeQualifiedIntegerFieldAccessorImpl的unsafe.putIntVolatile方法,但是结果却是不同的,原因就在于set方法中第二个参数是对象,

unsafe.putIntVolatile(arg0, this.fieldOffset, ((Integer) arg1).intValue());

所以有进行一次强转,然而调试发现强转也是调用的Integer.valueOf();,所以问题又回到了上面赋值的虽然是1,但是由于使用反射修改了value,导致取出来的值实际上是2,而setInt没有这个过程,所以setInt修改成功,但是set修改失败。

参考文章:

https://mp.weixin.qq.com/s/zTSivHhqPd73AQyhcOx5kQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

future_1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值