使用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