Loading

jdk之java.lang.Integer源码理解

基本数据类型的包装类java.lang.Integer是我们频繁使用的一个系统类,那么通过一个示例反应出的几个问题来深入理解一下此类的源码。

需求:实现Integer类型的两个数值交换。

 1 package cn.integer;
 2 
 3 public class Demo {
 4     public static void main(String[] args) {
 5         Integer a = 1;
 6         Integer b = 2;
 7         System.out.println("bofore swap a:"+a+",b:"+b);
 8         //交换a和b的值
 9         swap(a,b);
10         System.out.println("after swap a:"+a+",b:"+b);
11     }
12     /**
13      * 数据交换
14      * @param a
15      * @param b
16      */
17     private static void swap(Integer a, Integer b) {
18         /**
19          * 如何实现呢?尝试几种方法
20          */
21         /*01.方式一(无法实现)
22         a = a^b;
23         b = a^b;
24         a = a^b;*/
25         
26         /*02.方式二(无法实现)
27         int tmp = a;
28         a = b;
29         b = tmp;*/
30         
31         /**
32          * 以上两种方式是因为java的值传递特性故无法实现数据交换。
33          */
34     }
35 }

Java值传递的示意图如下:

当调用swap(..)方法时,在堆中会创建这两个值得副本,形参num1和num2指向副本的数据。原ab指向的数据不会改变。

 

那么如何通过修改swap()方法实现呢?

观察java.lang.Integer源码:

public final class Integer extends Number implements Comparable<Integer> {
          ...  
}

可以发现 Integer和String一样,是final修饰的,这是因为jdk将这些系统类封装起来不希望被破坏。

 

继续看Integer类:

 /**
     * The value of the {@code Integer}.
     *
     * @serial
     */
    private final int value;

    /**
     * Constructs a newly allocated {@code Integer} object that
     * represents the specified {@code int} value.
     *
     * @param   value   the value to be represented by the
     *                  {@code Integer} object.
     */
    public Integer(int value) {
        this.value = value;
    }

这里的value也是使用final修饰的,那么如果想修改它的值,可以使用反射的方式获取value字段并进行修改。

修改swap()方法中代码:

 1 public class Demo {
 2     public static void main(String[] args) {
 3         Integer a = 1;
 4         Integer b = 2;
 5         System.out.println("bofore swap a:"+a+",b:"+b);
 6         //交换a和b的值
 7         swap(a,b);
 8         System.out.println("after swap a:"+a+",b:"+b);
 9     }
10     /**
11      * 数据交换
12      * @param a
13      * @param b
14      */
15     private static void swap(Integer a, Integer b) {
16         try {
17             //获取Integer类中私有字段value
18             Field field = Integer.class.getDeclaredField("value");
19             /**
20              * 开启访问权限
21              * public final class Field extends AccessibleObject implements Member {}
22              * 跟进AccessibleObject
23              * public void setAccessible(boolean flag) throws SecurityException {
24                     SecurityManager sm = System.getSecurityManager();
25                     if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
26                     setAccessible0(this, flag);
27                 }
28                 
29                 private static void setAccessible0(AccessibleObject obj, boolean flag)
30                     throws SecurityException
31                     {
32                         if (obj instanceof Constructor && flag == true) {
33                         Constructor<?> c = (Constructor<?>)obj;
34                         if (c.getDeclaringClass() == Class.class) {
35                             throw new SecurityException("Cannot make a java.lang.Class" +
36                                                 " constructor accessible");
37                         }
38                     }
39                     obj.override = flag;
40                 }
41              */
42             field.setAccessible(true);
43             //临时变量
44             int tmp = a.intValue();
45             //数据交换
46             field.set(a, b);
47             field.set(b, tmp);
48         } catch (Exception e) {
49             e.printStackTrace();
50         }
51     }
52 }

 

运行结果:

诶,我去。怎么只改变了一个值?咋成功了一半呢?

 

01.首先,Integer a = 1; 这里会自动装箱,将 int类型的1 转换成 Integer类型。

通过valueOf方法进行装箱

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

这个方法非常重要,如果传入的参数在 -128-127之间,会返回一个缓存中的数据。否则就 new出一个Integer对象!

Integer.IntegerCache是Integer中的内部类:

 1 private static class IntegerCache {
 2         static final int low = -128;
 3         static final int high;
 4         static final Integer cache[];
 5 
 6         static {
 7             // high value may be configured by property
 8             int h = 127;
 9             String integerCacheHighPropValue =
10                 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
11             if (integerCacheHighPropValue != null) {
12                 try {
13                     int i = parseInt(integerCacheHighPropValue);
14                     i = Math.max(i, 127);
15                     // Maximum array size is Integer.MAX_VALUE
16                     h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
17                 } catch( NumberFormatException nfe) {
18                     // If the property cannot be parsed into an int, ignore it.
19                 }
20             }
21             high = h;
22 
23             cache = new Integer[(high - low) + 1];
24             int j = low;
25             for(int k = 0; k < cache.length; k++)
26                 cache[k] = new Integer(j++);
27 
28             // range [-128, 127] must be interned (JLS7 5.1.7)
29             assert IntegerCache.high >= 127;
30         }
31 
32         private IntegerCache() {}
33     }

 

02.当我们获取数值1时

return IntegerCache.cache[i + (-IntegerCache.low)] 
计算出IntegerCache.cache[129]的值(也就是1)返回。

cache这个数组存放着 -128-127这些数据。故index=129就是返回1。

03. 执行这行时 field.set(a, b);
将传入的2复制给a,此时a为2.
注意:这里通过set方法改变的的是缓存cache数组中的数据!

04. 当执行到 field.set(b, tmp);
tmp为1,而set方法的参数类型是Object,可以传入int类型的tmp
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);
    }

所以这个tmp=1是从Integer的缓存数组中取得,因上一行操作已经将cache中index为129位置得原数值1改变为了2,故在cache中获取的是2! 那么把2赋值给了b自然是2!(b对应的下标[130],a对应的下标[129])

 

我们发现,问题的关键在于这个缓存cache!

如果说可以避开走缓存这一步,我们就能实现数据交换。除了传入 -128-127之外的数据,我们还可以:

001.将tmp转换成Integer对象后在传入Field的set方法:

private static void swap(Integer a, Integer b) {
        try {
            //获取Integer类中私有字段value
            Field field = Integer.class.getDeclaredField("value");
            field.setAccessible(true);
            //临时变量
            int tmp = a.intValue();
            //数据交换
            field.set(a, b);
            field.set(b, new Integer(tmp));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

运行结果:

 

 002. 使用 Field类的 setInt方法
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);
    }

修改代码 field.setInt(b,tmp); 也可以实现。

003. 非要实现打印效果的话,使用非常规手短,直接在swap中打印结果...

private static void swap(Integer a, Integer b) {
        System.out.println("after swap a:2,b:1");
        System.exit(0);
    }

 

这个示例中涉及到技能点:

 

 
posted @ 2018-02-28 23:24  yanbubao  阅读(1215)  评论(0编辑  收藏  举报