使用void方法交换两个Integer整数
前提条件:
1.参数的传递方式:值传递和引用传递,其中值传递为基础数据类型,引用传递为 对象,数组,集合等
2.注意,这里要特殊考虑String,以及Integer、Double等几个基本类型包装类,它们都是immutable类型,因为没有提供自身修改的函数,每次操作都是新生成一个对象,所以要特殊对待,可以认为是和基本数据类型相似,传值操作 。因此,正常情况下,是值传递了,就没办法完本体的数值交换操作了。但这并不是一个真正的值传递,又给了这个交换提供了可能。那什么方法能把可能变成现实呢?反射,反射可以获取到私有方法,可以修改其访问权限,可以完成对其值得修改。当这也会有其他问题。先看下代码:
主方法:
public static void main(String[] args) {
Integer a = 10;
Integer b = 8;
swap0(a,b);
// swap1(a,b);
// swap2(a,b);
System.out.println("a="+a);
System.out.println("b="+b);
}第一种交换方法:
这个方法肯定不行,整体交换就像值传递一样,这里的c1,c2 是 传递过来的 a,b的副本,c1和a指向同一块内存(Integer:10),
c2和 b 指向同一块内存(Integer:8), c1 和 c2 做交换了,只是c1里指向变成了c2的,c2指向了c1。这并不影响a和b的指向
public static void swap0(Integer c1,Integer c2){
Integer tmp = c1;
c1 = c2;
c2 = tmp;
}
}
第二种: 从源码中得知 Integer 的值是由它的一个value属性决定。改变了value,那么这个对象就真实的发生变化了。
这就不是引用的的交换,是其指向的内存中的值变了。所以这会影响到 a和b的改变。
这种交换,是交换的 c1指向的内存中数据的改变,所以值真实的发生变化,
但从注释就能看出,这根本就不能实现,因为value的private final类型的,对外不可见,不可修改
那么 怎么办呢?如何获取私有的属性?,需要用到反射。
public static void swap1(Integer c1,Integer c2){
// int tmp = c1.value;
// c1.value = c2.value;
// c2.value = tmp;
}
第三种:反射,看样子是完美了,但也有几个点先通过
public static void swap2(Integer a,Integer b){
try {
// Integer c = a;
// Integer c = Integer.valueOf(a);
Integer c = new Integer(a);
// 通过反射获取私有属性,注意:getField和getDeclaredField的区别
Class<? extends Integer> clazz = Integer.class;
Field val = clazz.getDeclaredField("value");
//val.setAccessible(true);
val.set(a,b);
val.set(b,c);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}第三种遇到的问题
a.异常问题,IllegalAccessException异常,通常看异常都是从下往上,找到Field.set,进入方法

这里也有个抛出IlleagalArgumentException异常
@CallerSensitive
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);
}而且这个方法的注释中也说了,如果属性是final型的,该方法将抛出IllegalAccessException 除非使用setAccessible(true)。
虽然已经有这个解决方案了,还是再去看一下,具体是如何抛出异常的。

该异常时由于权限问题引起的,所以应该在checkAccess代码出进入程序(调试可以定位到,或者根据 抛出的异常信息也可以定位),
void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers)
throws IllegalAccessException
{
if (caller == clazz) { // quick check
return; // ACCESS IS OK
}
Object cache = securityCheckCache; // read volatile
Class<?> targetClass = clazz;
if (obj != null
&& Modifier.isProtected(modifiers)
&& ((targetClass = obj.getClass()) != clazz)) {
// Must match a 2-list of { caller, targetClass }.
if (cache instanceof Class[]) {
Class<?>[] cache2 = (Class<?>[]) cache;
if (cache2[1] == targetClass &&
cache2[0] == caller) {
return; // ACCESS IS OK
}
// (Test cache[1] first since range check for [1]
// subsumes range check for [0].)
}
} else if (cache == caller) {
// Non-protected case (or obj.class == this.clazz).
return; // ACCESS IS OK
}
// If no return, fall through to the slow path.
slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);
}void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers,
Class<?> targetClass)
throws IllegalAccessException
{
Reflection.ensureMemberAccess(caller, clazz, obj, modifiers);
// Success: Update the cache.
Object cache = ((targetClass == clazz)
? caller
: new Class<?>[] { caller, targetClass });
// Note: The two cache elements are not volatile,
// but they are effectively final. The Java memory model
// guarantees that the initializing stores for the cache
// elements will occur before the volatile write.
securityCheckCache = cache; // write volatile
} public static void ensureMemberAccess(Class<?> var0, Class<?> var1, Object var2, int var3) throws IllegalAccessException {
if(var0 != null && var1 != null) {
if(!verifyMemberAccess(var0, var1, var2, var3)) {
throw new IllegalAccessException("Class " + var0.getName() + " can not access a member of class " + var1.getName() + " with modifiers "" + Modifier.toString(var3) + """);
}
} else {
throw new InternalError();
}
}public static boolean verifyMemberAccess(Class<?> var0, Class<?> var1, Object var2, int var3) {
boolean var4 = false;
boolean var5 = false;
if(var0 == var1) {
return true;
} else {
if(!Modifier.isPublic(getClassAccessFlags(var1))) {
var5 = isSameClassPackage(var0, var1);
var4 = true;
if(!var5) {
return false;
}
}
if(Modifier.isPublic(var3)) {
return true;
} else {
boolean var6 = false;
if(Modifier.isProtected(var3) && isSubclassOf(var0, var1)) {
var6 = true;
}
if(!var6 && !Modifier.isPrivate(var3)) {
if(!var4) {
var5 = isSameClassPackage(var0, var1);
var4 = true;
}
if(var5) {
var6 = true;
}
}
if(!var6) {
return false;
} else {
if(Modifier.isProtected(var3)) {
Class var7 = var2 == null?var1:var2.getClass();
if(var7 != var0) {
if(!var4) {
var5 = isSameClassPackage(var0, var1);
var4 = true;
}
if(!var5 && !isSubclassOf(var7, var0)) {
return false;
}
}
}
return true;
}
}
}
}这上面代码的中,如下图的地方,由于var3是私有的,这里确定了一点,这并不是因为final而报的错。

解决办法上面已经说了,仍然使用 val.setAccessible(true);
到这里是不是说整个分析就结束了,接着看
如果swap2中的中间变量c(用于保存a的值的),如果使用Integer c = a; 结果会咋样?
D:SystemSoftwareJavajdk1.8.0_131injava ... a=8 b=8
为什么会这样?因为这样赋值 c 和 a 指向的内存中的对象是一个,所以a变得同时,c也变了。
如果使用Integer c = Integer.valueOf(a); 结果会咋样?
D:SystemSoftwareJavajdk1.8.0_131injava ... a=8 b=8
难道结果与Intger c =a 是一样的?再换个值试下,
把需要交换的a和b换成 a = 128, b=135;
D:SystemSoftwareJavajdk1.8.0_131injava ... a=135 b=128
有成功了?为啥呢,看下Integer源码
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}原因在IntegerCache,终于搞懂了,如果值在 -128到127(值也可以设置)之间 是从IntegerCache中取的。
那如何保证数值正确呢?像它最后一行代码那样,new Integer(a).
这么做后有问题么?
再将值改回 a= 10; b=8; 在主方法中再次输出
public static void main(String[] args) {
Integer a = 10;
Integer b = 8;
// Integer c = 132;
// Integer d = 10;
// swap0(a,b);
// swap1(a,b);
swap2(a,b);
System.out.println("a="+a);
System.out.println("b="+b);
Integer m = Integer.valueOf(10);
Integer n = 8;
System.out.println("m="+m);
System.out.println("n="+n);
}D:SystemSoftwareJavajdk1.8.0_131injava ... a=8 b=10 m=8 n=10
为什么 m和n输出的不正确呢?m和n都处于 -128 < a, b <127之间。
他们的值都来自IntegerCache,而IntegerCache是个数组,使用的时候按下标取值,而处于原来10的位置被swap方法给改成8,同里8也被改成了10.
具体验证可以输出看下,这里就不继续了。
最终源码
package com.dl.study.m1.d1;
import java.lang.reflect.Field;
public class ExchangeInteger {
public static void main(String[] args) {
Integer a = 10;
Integer b = 8;
// Integer c = 132;
// Integer d = 10;
// swap0(a,b);
// swap1(a,b);
swap2(a,b);
System.out.println("a="+a);
System.out.println("b="+b);
Integer m = Integer.valueOf(10);
Integer n = 8;
System.out.println("m="+m);
System.out.println("n="+n);
}
public static void swap0(Integer c1,Integer c2){
Integer tmp = c1;
c1 = c2;
c2 = tmp;
//这个方法肯定不行,整体交换就像值传递一样,这里的c1,c2 是 传递过来的 a,b的副本,c1和a指向同一块内存(Integer:10),
//c2和 b 指向同一块内存(Integer:8), c1 和 c2 做交换了,只是c1里指向变成了c2的,c2指向了c1。这并不影响a和b的指向
}
public static void swap1(Integer c1,Integer c2){
// int tmp = c1.value;
// c1.value = c2.value;
// c2.value = tmp;
// 从源码中得知 Integer 的值是由它的一个value属性决定。改变了value,那么这个对象就真实的发生变化了。
// 这就不是引用的的交换,是其指向的内存中的值变了。所以这会影响到 a和b的改变。
// 这种交换,是交换的 c1指向的内存中数据的改变,所以值真实的发生变化,
// 但从注释就能看出,这根本就不能实现,因为value的private final类型的,对外不可见,不可修改
// 那么 怎么办呢?如何获取私有的属性?
}
public static void swap2(Integer a,Integer b){
try {
// Integer c = a;
// Integer c = Integer.valueOf(a);
Integer c = new Integer(a);
// 通过反射获取私有属性,注意:getField和getDeclaredField的区别
Class<? extends Integer> clazz = Integer.class;
Field val = clazz.getDeclaredField("value");
val.setAccessible(true);
val.set(a,b);
val.set(b,c);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}参考资料:值传递和引用传递 https://www.cnblogs.com/binyue/p/3862276.html
字节码指令:https://www.cnblogs.com/luyanliang/p/5498584.html