当前位置:首页 > 未命名 > 正文内容

使用void方法交换两个Integer整数

淙嶙7年前 (2018-07-25)未命名476

前提条件:

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,进入方法

image.png


这里也有个抛出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)。

虽然已经有这个解决方案了,还是再去看一下,具体是如何抛出异常的。

image.png

 

该异常时由于权限问题引起的,所以应该在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而报的错。

image.png




解决办法上面已经说了,仍然使用 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


相关文章

...

sonarqube安装使用

sonarqube安装使用

必要条件 JDK8(Oracle JRE8 或者OpenJDK8) 硬件需求,非企业版的要求特别低,直接忽略了,企业版的需要8核CPU、16GBRAM。 支持的平台 JDK:Oracle...

生成SSH秘钥

生成SSH秘钥

SSH是建立在应用层和传输层基础上的安全协议,其目的是专为远程登录会话和其他网络服务提供安全性的保障,用过SSH远程登录的人都比较熟悉,可以认为SSH是一种安全的Shell。Http登录是需要用户名和...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。